From 343b52ca3b3df43ebd442bc15c2502a9abb3b8d1 Mon Sep 17 00:00:00 2001 From: James Arthur Date: Tue, 29 Oct 2024 15:04:16 +0100 Subject: [PATCH] api: update header names and query params. --- .../react-hooks/test/support/test-context.ts | 2 +- .../lib/electric/plug/delete_shape_plug.ex | 6 +-- .../lib/electric/plug/serve_shape_plug.ex | 42 +++++++++---------- .../test/electric/plug/router_test.exs | 17 ++++---- .../electric/plug/serve_shape_plug_test.exs | 20 ++++----- packages/typescript-client/src/constants.ts | 17 ++++---- packages/typescript-client/test/cache.test.ts | 14 +++---- .../test/integration.test.ts | 6 +-- website/docs/api/http.md | 2 +- website/docs/quickstart.md | 7 ++-- website/electric-api.yaml | 39 +++++++++++++++-- 11 files changed, 103 insertions(+), 69 deletions(-) diff --git a/packages/react-hooks/test/support/test-context.ts b/packages/react-hooks/test/support/test-context.ts index 3f236a15f1..e6bac9d065 100644 --- a/packages/react-hooks/test/support/test-context.ts +++ b/packages/react-hooks/test/support/test-context.ts @@ -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`, } diff --git a/packages/sync-service/lib/electric/plug/delete_shape_plug.ex b/packages/sync-service/lib/electric/plug/delete_shape_plug.ex index a6981b3f66..3c8ed59524 100644 --- a/packages/sync-service/lib/electric/plug/delete_shape_plug.ex +++ b/packages/sync-service/lib/electric/plug/delete_shape_plug.ex @@ -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 @@ -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 diff --git a/packages/sync-service/lib/electric/plug/serve_shape_plug.ex b/packages/sync-service/lib/electric/plug/serve_shape_plug.ex index 9c1cca69dd..5379c172b1 100644 --- a/packages/sync-service/lib/electric/plug/serve_shape_plug.ex +++ b/packages/sync-service/lib/electric/plug/serve_shape_plug.ex @@ -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) @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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() @@ -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( @@ -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 @@ -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() ) @@ -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() @@ -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 diff --git a/packages/sync-service/test/electric/plug/router_test.exs b/packages/sync-service/test/electric/plug/router_test.exs index 7ba17e8ee4..2ae9e74a19 100644 --- a/packages/sync-service/test/electric/plug/router_test.exs +++ b/packages/sync-service/test/electric/plug/router_test.exs @@ -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) @@ -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( @@ -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 = @@ -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" @@ -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: [ @@ -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) diff --git a/packages/sync-service/test/electric/plug/serve_shape_plug_test.exs b/packages/sync-service/test/electric/plug/serve_shape_plug_test.exs index 1ed737a35d..42b8835888 100644 --- a/packages/sync-service/test/electric/plug/serve_shape_plug_test.exs +++ b/packages/sync-service/test/electric/plug/serve_shape_plug_test.exs @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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" @@ -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 diff --git a/packages/typescript-client/src/constants.ts b/packages/typescript-client/src/constants.ts index 67486a27cd..49e13968a2 100644 --- a/packages/typescript-client/src/constants.ts +++ b/packages/typescript-client/src/constants.ts @@ -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` \ No newline at end of file diff --git a/packages/typescript-client/test/cache.test.ts b/packages/typescript-client/test/cache.test.ts index d19c02fc21..250b44a810 100644 --- a/packages/typescript-client/test/cache.test.ts +++ b/packages/typescript-client/test/cache.test.ts @@ -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`, }) @@ -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() @@ -227,7 +227,7 @@ describe(`HTTP Initial Data Caching`, { timeout: 30000 }, () => { ) expect(client2Res.status).toBe(200) const shapeHandle2 = - client2Res.headers.get(`electric-shape-handle`) ?? undefined + client2Res.headers.get(`electric-handle`) ?? undefined expect( originalHandleId, @@ -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 @@ -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( - `electric-shape-handle` + `electric-handle` ) assert(cacheBustedShapeHandle) expect(cacheBustedShapeHandle).not.toBe(originalHandleId) @@ -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( diff --git a/packages/typescript-client/test/integration.test.ts b/packages/typescript-client/test/integration.test.ts index 0bbe5c548b..0d9bbd5210 100644 --- a/packages/typescript-client/test/integration.test.ts +++ b/packages/typescript-client/test/integration.test.ts @@ -119,7 +119,7 @@ describe(`HTTP Sync`, () => { `${BASE_URL}/v1/shape/${issuesTableUrl}?offset=-1`, {} ) - const shapeHandle = res.headers.get(`electric-shape-handle`) + const shapeHandle = res.headers.get(`electric-handle`) expect(shapeHandle).to.exist }) @@ -130,7 +130,7 @@ describe(`HTTP Sync`, () => { `${BASE_URL}/v1/shape/${issuesTableUrl}?offset=-1`, {} ) - const lastOffset = res.headers.get(`electric-chunk-last-offset`) + const lastOffset = res.headers.get(`electric-offset`) expect(lastOffset).to.exist }) @@ -590,7 +590,7 @@ describe(`HTTP Sync`, () => { const midMessage = messages.slice(-6)[0] assert(`offset` in midMessage) const midOffset = midMessage.offset - const shapeHandle = res.headers.get(`electric-shape-handle`) + const shapeHandle = res.headers.get(`electric-handle`) const etag = res.headers.get(`etag`) assert(etag !== null, `Response should have etag header`) diff --git a/website/docs/api/http.md b/website/docs/api/http.md index 7c3c78b283..21cf27a910 100644 --- a/website/docs/api/http.md +++ b/website/docs/api/http.md @@ -62,7 +62,7 @@ When you make an initial sync request, with `offset=-1`, you're telling the serv When a shape is first requested, Electric queries Postgres for the data and populates the log by turning the query results into insert operations. This allows you to sync shapes without having to pre-define them. Electric then streams out the log data in the response. -Sometimes a log can fit in a single response. Sometimes it's too big and requires multiple requests. In this case, the first request will return a batch of data and an `x-electric-chunk-last-offset` header. An HTTP client should then continue to make requests setting the `offset` parameter to the this header value. This allows the client to paginate through the shape log until it has received all the current data. +Sometimes a log can fit in a single response. Sometimes it's too big and requires multiple requests. In this case, the first request will return a batch of data and an `electric-offset` header. An HTTP client should then continue to make requests setting the `offset` parameter to the this header value. This allows the client to paginate through the shape log until it has received all the current data. ### Control messages diff --git a/website/docs/quickstart.md b/website/docs/quickstart.md index 4f2dc91373..160315e184 100644 --- a/website/docs/quickstart.md +++ b/website/docs/quickstart.md @@ -115,9 +115,10 @@ access-control-allow-origin: * access-control-expose-headers: * access-control-allow-methods: GET, POST, OPTIONS content-type: application/json; charset=utf-8 -x-electric-shape-handle: 3833821-1721299734314 -x-electric-chunk-last-offset: 0_0 -x-electric-schema: {"id":{"type":"int4","pk_index":0},"name":{"type":"varchar","max_length":255},"value":{"type":"float8"}} +electric-handle: 3833821-1721299734314 +electric-offset: 0_0 +electric-schema: {"id":{"type":"int4","pk_index":0},"name":{"type":"varchar","max_length":255},"value":{"type":"float8"}} +electric-up-to-date: etag: 3833821-1721299734314:-1:0_0 [{"offset":"0_0","value":{"id":"1","name":"Alice","value":"3.14"},"key":"\"public\".\"foo\"/1","headers":{"operation" diff --git a/website/electric-api.yaml b/website/electric-api.yaml index 402c1b8074..a1bdf5a290 100644 --- a/website/electric-api.yaml +++ b/website/electric-api.yaml @@ -167,7 +167,17 @@ paths: Etag header specifying the shape handle and offset for efficient caching. In the format `{shape_handle}:{start_offset}:{end_offset}`. - electric-chunk-last-offset: + electric-cursor: + schema: + type: string + example: "1674440" + description: |- + If present, provides a cursor to use as the value of the `cursor` + parameter in the next `live` mode long polling request. + + This works around some inconsistent request coalescing behaviour + with different CDNs. + electric-offset: schema: type: string example: "26800584_4" @@ -178,19 +188,22 @@ paths: you have provided. This header simplifies client development by avoiding the need to parse the last offset out of the stream of log entries. - electric-shape-handle: + + Must be used as the value of the `offset` parameter in your + next request. + electric-handle: schema: type: string example: "3833821-1721812114261" description: |- The shape handle. - Must be provided as the `shape_handle` parameter when making + Must be provided as the value of the `handle` parameter when making subsequent requests where `offset` is not `-1`. electric-schema: schema: type: string - example: '{"id":{"type":"int4","dimensions":0},"title":{"type":"text","dimensions":0},"status":{"type":"text","dimensions":0,"max_length":8}}' + example: '":0},"status":{"type":"text","dimensions":0,"max_length":8}}' description: |- A JSON string of an object that maps column names to the corresponding schema object. The schema object contains the type of the column, the number of dimensions, and possibly additional properties. @@ -204,6 +217,13 @@ paths: `INTERVAL(4)` has an additional `"precision": 4` property, `INTERVAL MINUTE TO SECOND` has an additional `"fields": "MINUTE TO SECOND"` property, `BIT(5)` has an additional `"length": 5` property. + electric-up-to-date: + schema: + description: |- + If present, this header indicates that the response ends with + an `up-to-date` control message, indicating that the client has + recieved all of the data that the server is aware of and can + safely process/apply any accumulated messages. content: application/json: schema: @@ -291,6 +311,17 @@ paths: description: >- No content. The `live=true` polling request timed out without any new content to process. + headers: + electric-cursor: + schema: + type: string + example: "1674440" + description: |- + Provides a cursor to use as the value of the `cursor` parameter + in the next long polling request. + + This works around some inconsistent request coalescing behaviour + with different CDNs. "400": description: Bad request. "409":