Skip to content

Commit

Permalink
api: update header names and query params.
Browse files Browse the repository at this point in the history
  • Loading branch information
thruflo committed Oct 29, 2024
1 parent 773c2aa commit 343b52c
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 69 deletions.
2 changes: 1 addition & 1 deletion packages/react-hooks/test/support/test-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const testWithDbClient = test.extend<{
use(async (table: string, shapeHandle?: string) => {
const baseUrl = inject(`baseUrl`)
const resp = await fetch(
`${baseUrl}/v1/shape/${table}${shapeHandle ? `?shape_handle=${shapeHandle}` : ``}`,
`${baseUrl}/v1/shape/${table}${shapeHandle ? `?handle=${shapeHandle}` : ``}`,
{
method: `DELETE`,
}
Expand Down
6 changes: 3 additions & 3 deletions packages/sync-service/lib/electric/plug/delete_shape_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ defmodule Electric.Plug.DeleteShapePlug do
defp validate_query_params(%Plug.Conn{} = conn, _) do
all_params =
Map.merge(conn.query_params, conn.path_params)
|> Map.take(["root_table", "shape_handle"])
|> Map.take(["root_table", "handle"])
|> Map.put("offset", "-1")

case Params.validate(all_params, inspector: conn.assigns.config[:inspector]) do
Expand All @@ -41,8 +41,8 @@ defmodule Electric.Plug.DeleteShapePlug do
end

defp truncate_or_delete_shape(%Plug.Conn{} = conn, _) do
if conn.assigns.shape_handle !== nil do
with :ok <- Shapes.clean_shape(conn.assigns.shape_handle, conn.assigns.config) do
if conn.assigns.handle !== nil do
with :ok <- Shapes.clean_shape(conn.assigns.handle, conn.assigns.config) do
send_resp(conn, 202, "")
end
else
Expand Down
42 changes: 21 additions & 21 deletions packages/sync-service/lib/electric/plug/serve_shape_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ defmodule Electric.Plug.ServeShapePlug do
embedded_schema do
field(:root_table, :string)
field(:offset, :string)
field(:shape_handle, :string)
field(:handle, :string)
field(:live, :boolean, default: false)
field(:where, :string)
field(:columns, :string)
Expand All @@ -82,7 +82,7 @@ defmodule Electric.Plug.ServeShapePlug do
|> validate_required([:root_table, :offset])
|> cast_offset()
|> cast_columns()
|> validate_shape_handle_with_offset()
|> validate_handle_with_offset()
|> validate_live_with_offset()
|> cast_root_table(opts)
|> apply_action(:validate)
Expand Down Expand Up @@ -129,16 +129,16 @@ defmodule Electric.Plug.ServeShapePlug do
end
end

def validate_shape_handle_with_offset(%Ecto.Changeset{valid?: false} = changeset),
def validate_handle_with_offset(%Ecto.Changeset{valid?: false} = changeset),
do: changeset

def validate_shape_handle_with_offset(%Ecto.Changeset{} = changeset) do
def validate_handle_with_offset(%Ecto.Changeset{} = changeset) do
offset = fetch_change!(changeset, :offset)

if offset == LogOffset.before_all() do
changeset
else
validate_required(changeset, [:shape_handle], message: "can't be blank when offset != -1")
validate_required(changeset, [:handle], message: "can't be blank when offset != -1")
end
end

Expand Down Expand Up @@ -221,9 +221,9 @@ defmodule Electric.Plug.ServeShapePlug do
end)
end

# No shape_handle is provided so we can get the existing one for this shape
# No handle is provided so we can get the existing one for this shape
# or create a new shape if it does not yet exist
defp get_or_create_shape_handle(%{shape_definition: shape, config: config, shape_handle: nil}) do
defp get_or_create_shape_handle(%{shape_definition: shape, config: config, handle: nil}) do
Shapes.get_or_create_shape_handle(config, shape)
end

Expand All @@ -233,7 +233,7 @@ defmodule Electric.Plug.ServeShapePlug do
end

defp handle_shape_info(
%Conn{assigns: %{shape_definition: shape, config: config, shape_handle: shape_handle}} =
%Conn{assigns: %{shape_definition: shape, config: config, handle: shape_handle}} =
conn,
nil
) do
Expand All @@ -257,7 +257,7 @@ defmodule Electric.Plug.ServeShapePlug do
end

defp handle_shape_info(
%Conn{assigns: %{shape_handle: shape_handle}} = conn,
%Conn{assigns: %{handle: shape_handle}} = conn,
{active_shape_handle, last_offset}
)
when is_nil(shape_handle) or shape_handle == active_shape_handle do
Expand All @@ -266,11 +266,11 @@ defmodule Electric.Plug.ServeShapePlug do
conn
|> assign(:active_shape_handle, active_shape_handle)
|> assign(:last_offset, last_offset)
|> put_resp_header("electric-shape-handle", active_shape_handle)
|> put_resp_header("electric-handle", active_shape_handle)
end

defp handle_shape_info(
%Conn{assigns: %{shape_handle: shape_handle, config: config}} = conn,
%Conn{assigns: %{handle: shape_handle, config: config}} = conn,
{active_shape_handle, _}
) do
if Shapes.has_shape?(config, shape_handle) do
Expand All @@ -280,17 +280,17 @@ defmodule Electric.Plug.ServeShapePlug do
|> send_resp(400, @shape_definition_mismatch)
|> halt()
else
# The requested shape_handle is not found, returns 409 along with a location redirect for clients to
# The requested handle is not found, returns 409 along with a location redirect for clients to
# re-request the shape from scratch with the new shape handle which acts as a consistent cache buster
# e.g. GET /v1/shape/{root_table}?shape_handle={new_shape_handle}&offset=-1
# e.g. GET /v1/shape/{root_table}?handle={new_handle}&offset=-1

# TODO: discuss returning a 307 redirect rather than a 409, the client
# will have to detect this and throw out old data
conn
|> put_resp_header("electric-shape-handle", active_shape_handle)
|> put_resp_header("electric-handle", active_shape_handle)
|> put_resp_header(
"location",
"#{conn.request_path}?shape_handle=#{active_shape_handle}&offset=-1"
"#{conn.request_path}?handle=#{active_shape_handle}&offset=-1"
)
|> send_resp(409, @must_refetch)
|> halt()
Expand Down Expand Up @@ -323,7 +323,7 @@ defmodule Electric.Plug.ServeShapePlug do

conn
|> assign(:chunk_end_offset, chunk_end_offset)
|> put_resp_header("electric-chunk-last-offset", "#{chunk_end_offset}")
|> put_resp_header("electric-offset", "#{chunk_end_offset}")
end

defp determine_up_to_date(
Expand All @@ -344,11 +344,11 @@ defmodule Electric.Plug.ServeShapePlug do
|> assign(:up_to_date, [])
# header might have been added on first pass but no longer valid
# if listening to live changes and an incomplete chunk is formed
|> delete_resp_header("electric-chunk-up-to-date")
|> delete_resp_header("electric-up-to-date")
else
conn
|> assign(:up_to_date, [@up_to_date])
|> put_resp_header("electric-chunk-up-to-date", "")
|> put_resp_header("electric-up-to-date", "")
end
end

Expand Down Expand Up @@ -407,7 +407,7 @@ defmodule Electric.Plug.ServeShapePlug do
"public, max-age=5, stale-while-revalidate=5"
)
|> put_resp_header(
"electric-next-cursor",
"electric-cursor",
TimeUtils.seconds_since_oct9th_2024_next_interval(conn) |> Integer.to_string()
)

Expand Down Expand Up @@ -571,7 +571,7 @@ defmodule Electric.Plug.ServeShapePlug do
|> assign(:last_offset, latest_log_offset)
|> assign(:chunk_end_offset, latest_log_offset)
# update last offset header
|> put_resp_header("electric-chunk-last-offset", "#{latest_log_offset}")
|> put_resp_header("electric-offset", "#{latest_log_offset}")
|> determine_up_to_date([])
|> serve_shape_log()

Expand All @@ -597,7 +597,7 @@ defmodule Electric.Plug.ServeShapePlug do
if is_struct(conn.query_params, Plug.Conn.Unfetched) do
assigns[:active_shape_handle] || assigns[:shape_handle]
else
conn.query_params["shape_handle"] || assigns[:active_shape_handle] ||
conn.query_params["handle"] || assigns[:active_shape_handle] ||
assigns[:shape_handle]
end

Expand Down
17 changes: 9 additions & 8 deletions packages/sync-service/test/electric/plug/router_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,9 @@ defmodule Electric.Plug.RouterTest do

conn = conn("GET", "/v1/shape/large_rows_table?offset=-1") |> Router.call(opts)
assert %{status: 200} = conn
[shape_handle] = Plug.Conn.get_resp_header(conn, "electric-shape-handle")
[next_offset] = Plug.Conn.get_resp_header(conn, "electric-chunk-last-offset")

[shape_handle] = Plug.Conn.get_resp_header(conn, "electric-handle")
[next_offset] = Plug.Conn.get_resp_header(conn, "electric-offset")

assert [] = Jason.decode!(conn.resp_body)

Expand Down Expand Up @@ -733,7 +734,7 @@ defmodule Electric.Plug.RouterTest do
}
] = Jason.decode!(conn.resp_body)

[next_offset] = Plug.Conn.get_resp_header(conn, "electric-chunk-last-offset")
[next_offset] = Plug.Conn.get_resp_header(conn, "electric-offset")

conn =
conn(
Expand Down Expand Up @@ -771,7 +772,7 @@ defmodule Electric.Plug.RouterTest do
assert conn.resp_body != ""

shape_handle = get_resp_shape_handle(conn)
[next_offset] = Plug.Conn.get_resp_header(conn, "electric-chunk-last-offset")
[next_offset] = Plug.Conn.get_resp_header(conn, "electric-offset")

# Make the next request but forget to include the where clause
conn =
Expand All @@ -798,7 +799,7 @@ defmodule Electric.Plug.RouterTest do

assert %{status: 409} = conn
assert conn.resp_body == Jason.encode!([%{headers: %{control: "must-refetch"}}])
new_shape_handle = get_resp_header(conn, "electric-shape-handle")
new_shape_handle = get_resp_header(conn, "electric-handle")

assert get_resp_header(conn, "location") ==
"/v1/shape/items?shape_handle=#{new_shape_handle}&offset=-1"
Expand Down Expand Up @@ -827,7 +828,7 @@ defmodule Electric.Plug.RouterTest do
|> Router.call(opts)

assert %{status: 409} = conn
[^shape_handle] = Plug.Conn.get_resp_header(conn, "electric-shape-handle")
[^shape_handle] = Plug.Conn.get_resp_header(conn, "electric-handle")
end

@tag with_sql: [
Expand Down Expand Up @@ -898,8 +899,8 @@ defmodule Electric.Plug.RouterTest do
end
end

defp get_resp_shape_handle(conn), do: get_resp_header(conn, "electric-shape-handle")
defp get_resp_last_offset(conn), do: get_resp_header(conn, "electric-chunk-last-offset")
defp get_resp_shape_handle(conn), do: get_resp_header(conn, "electric-handle")
defp get_resp_last_offset(conn), do: get_resp_header(conn, "electric-offset")

defp get_resp_header(conn, header) do
assert [val] = Plug.Conn.get_resp_header(conn, header)
Expand Down
20 changes: 10 additions & 10 deletions packages/sync-service/test/electric/plug/serve_shape_plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ defmodule Electric.Plug.ServeShapePlugTest do
"#{@test_shape_handle}:-1:#{next_offset}"
]

assert Plug.Conn.get_resp_header(conn, "electric-shape-handle") == [@test_shape_handle]
assert Plug.Conn.get_resp_header(conn, "electric-handle") == [@test_shape_handle]
end

test "snapshot has correct cache control headers" do
Expand Down Expand Up @@ -349,13 +349,13 @@ defmodule Electric.Plug.ServeShapePlugTest do
"#{@test_shape_handle}:#{@start_offset_50}:#{next_next_offset}"
]

assert Plug.Conn.get_resp_header(conn, "electric-shape-handle") == [@test_shape_handle]
assert Plug.Conn.get_resp_header(conn, "electric-handle") == [@test_shape_handle]

assert Plug.Conn.get_resp_header(conn, "electric-chunk-last-offset") == [
assert Plug.Conn.get_resp_header(conn, "electric-offset") == [
"#{next_next_offset}"
]

assert Plug.Conn.get_resp_header(conn, "electric-chunk-up-to-date") == []
assert Plug.Conn.get_resp_header(conn, "electric-up-to-date") == []
end

test "returns 304 Not Modified when If-None-Match matches ETag" do
Expand Down Expand Up @@ -444,8 +444,8 @@ defmodule Electric.Plug.ServeShapePlugTest do
"public, max-age=5, stale-while-revalidate=5"
]

assert Plug.Conn.get_resp_header(conn, "electric-chunk-last-offset") == [next_offset_str]
assert Plug.Conn.get_resp_header(conn, "electric-chunk-up-to-date") == [""]
assert Plug.Conn.get_resp_header(conn, "electric-offset") == [next_offset_str]
assert Plug.Conn.get_resp_header(conn, "electric-up-to-date") == [""]
assert Plug.Conn.get_resp_header(conn, "electric-schema") == []
end

Expand Down Expand Up @@ -494,7 +494,7 @@ defmodule Electric.Plug.ServeShapePlugTest do

assert conn.status == 200
assert Jason.decode!(conn.resp_body) == [%{"headers" => %{"control" => "up-to-date"}}]
assert Plug.Conn.get_resp_header(conn, "electric-chunk-up-to-date") == [""]
assert Plug.Conn.get_resp_header(conn, "electric-up-to-date") == [""]
end

test "sends an up-to-date response after a timeout if no changes are observed" do
Expand Down Expand Up @@ -530,7 +530,7 @@ defmodule Electric.Plug.ServeShapePlugTest do
"public, max-age=5, stale-while-revalidate=5"
]

assert Plug.Conn.get_resp_header(conn, "electric-chunk-up-to-date") == [""]
assert Plug.Conn.get_resp_header(conn, "electric-up-to-date") == [""]
end

test "sends 409 with a redirect to existing shape when requested shape handle does not exist" do
Expand All @@ -554,7 +554,7 @@ defmodule Electric.Plug.ServeShapePlugTest do
assert conn.status == 409

assert Jason.decode!(conn.resp_body) == [%{"headers" => %{"control" => "must-refetch"}}]
assert get_resp_header(conn, "electric-shape-handle") == [@test_shape_handle]
assert get_resp_header(conn, "electric-handle") == [@test_shape_handle]

assert get_resp_header(conn, "location") == [
"/?shape_handle=#{@test_shape_handle}&offset=-1"
Expand Down Expand Up @@ -585,7 +585,7 @@ defmodule Electric.Plug.ServeShapePlugTest do
assert conn.status == 409

assert Jason.decode!(conn.resp_body) == [%{"headers" => %{"control" => "must-refetch"}}]
assert get_resp_header(conn, "electric-shape-handle") == [new_shape_handle]
assert get_resp_header(conn, "electric-handle") == [new_shape_handle]
assert get_resp_header(conn, "location") == ["/?shape_handle=#{new_shape_handle}&offset=-1"]
end

Expand Down
17 changes: 9 additions & 8 deletions packages/typescript-client/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export const SHAPE_HANDLE_HEADER = `electric-shape-handle`
export const LIVE_CACHE_BUSTER_HEADER = `electric-next-cursor`
export const LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`
export const CHUNK_LAST_OFFSET_HEADER = `electric-chunk-last-offset`
export const CHUNK_UP_TO_DATE_HEADER = `electric-chunk-up-to-date`
export const LIVE_CACHE_BUSTER_HEADER = `electric-cursor`
export const SHAPE_HANDLE_HEADER = `electric-handle`
export const CHUNK_LAST_OFFSET_HEADER = `electric-offset`
export const SHAPE_SCHEMA_HEADER = `electric-schema`
export const SHAPE_HANDLE_QUERY_PARAM = `shape_handle`
export const OFFSET_QUERY_PARAM = `offset`
export const WHERE_QUERY_PARAM = `where`
export const CHUNK_UP_TO_DATE_HEADER = `electric-up-to-date`

export const COLUMNS_QUERY_PARAM = `columns`
export const LIVE_CACHE_BUSTER_QUERY_PARAM = `cursor`
export const SHAPE_HANDLE_QUERY_PARAM = `handle`
export const LIVE_QUERY_PARAM = `live`
export const OFFSET_QUERY_PARAM = `offset`
export const WHERE_QUERY_PARAM = `where`

Check failure on line 12 in packages/typescript-client/src/constants.ts

View workflow job for this annotation

GitHub Actions / Check packages/typescript-client

Insert `⏎`
14 changes: 7 additions & 7 deletions packages/typescript-client/test/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ describe(`HTTP Proxy Cache`, { timeout: 30000 }, () => {
// add some data and follow with live request
await insertIssues({ title: `foo` })
const searchParams = new URLSearchParams({
offset: initialRes.headers.get(`electric-chunk-last-offset`)!,
shape_handle: initialRes.headers.get(`electric-shape-handle`)!,
handle: initialRes.headers.get(`electric-handle`)!,
offset: initialRes.headers.get(`electric-offset`)!,
live: `true`,
})

Expand Down Expand Up @@ -214,7 +214,7 @@ describe(`HTTP Initial Data Caching`, { timeout: 30000 }, () => {
)
expect(client1Res.status).toBe(200)
const originalHandleId =
client1Res.headers.get(`electric-shape-handle`) ?? undefined
client1Res.headers.get(`electric-handle`) ?? undefined
assert(originalHandleId, `Should have shape handle`)
expect(getCacheStatus(client1Res)).toBe(CacheStatus.MISS)
//const messages = client1Res.status === 204 ? [] : await client1Res.json()
Expand All @@ -227,7 +227,7 @@ describe(`HTTP Initial Data Caching`, { timeout: 30000 }, () => {
)
expect(client2Res.status).toBe(200)
const shapeHandle2 =

Check failure on line 229 in packages/typescript-client/test/cache.test.ts

View workflow job for this annotation

GitHub Actions / Check packages/typescript-client

Delete `⏎·····`
client2Res.headers.get(`electric-shape-handle`) ?? undefined
client2Res.headers.get(`electric-handle`) ?? undefined

expect(
originalHandleId,
Expand All @@ -236,7 +236,7 @@ describe(`HTTP Initial Data Caching`, { timeout: 30000 }, () => {

expect(getCacheStatus(client2Res)).toBe(CacheStatus.HIT)

const latestOffset = client2Res.headers.get(`electric-chunk-last-offset`)
const latestOffset = client2Res.headers.get(`electric-offset`)
assert(latestOffset, `latestOffset should be defined`)

// Now GC the shape
Expand All @@ -261,7 +261,7 @@ describe(`HTTP Initial Data Caching`, { timeout: 30000 }, () => {
expect(newCacheIgnoredSyncRes.status).toBe(200)
expect(getCacheStatus(newCacheIgnoredSyncRes)).toBe(CacheStatus.MISS)
const cacheBustedShapeHandle = newCacheIgnoredSyncRes.headers.get(

Check failure on line 263 in packages/typescript-client/test/cache.test.ts

View workflow job for this annotation

GitHub Actions / Check packages/typescript-client

Replace `·newCacheIgnoredSyncRes.headers.get(⏎······`electric-handle`⏎····` with `⏎······newCacheIgnoredSyncRes.headers.get(`electric-handle``
`electric-shape-handle`
`electric-handle`
)
assert(cacheBustedShapeHandle)
expect(cacheBustedShapeHandle).not.toBe(originalHandleId)
Expand All @@ -272,7 +272,7 @@ describe(`HTTP Initial Data Caching`, { timeout: 30000 }, () => {
{}
)
const cachedShapeHandle =
newInitialSyncRes.headers.get(`electric-shape-handle`) ?? undefined
newInitialSyncRes.headers.get(`electric-handle`) ?? undefined
expect(newInitialSyncRes.status).toBe(200)
expect(getCacheStatus(newInitialSyncRes)).toBe(CacheStatus.HIT)
expect(
Expand Down
Loading

0 comments on commit 343b52c

Please sign in to comment.