diff --git a/.changeset/cuddly-rats-invent.md b/.changeset/cuddly-rats-invent.md new file mode 100644 index 0000000000..fdd437655c --- /dev/null +++ b/.changeset/cuddly-rats-invent.md @@ -0,0 +1,7 @@ +--- +"@electric-sql/client": minor +"@core/sync-service": minor +"@electric-sql/react": minor +--- + +Fix: rename "action" header -> "operation" to match Postgres's name for inserts, updates, deletes diff --git a/docs/electric-api.yaml b/docs/electric-api.yaml index 0fa972f192..5d6eae20d8 100644 --- a/docs/electric-api.yaml +++ b/docs/electric-api.yaml @@ -202,14 +202,14 @@ paths: Messages can be `control` messages, providing information or instructions to the client. Or they can be operations that - performed a certain `action` on a row of data in the shape. + performed a certain `operation` on a row of data in the shape. properties: control: type: string enum: - up-to-date - must-refetch - action: + operation: type: string enum: - insert @@ -254,7 +254,7 @@ paths: - `extra_float_digits = 1` example: - headers: - action: insert + operation: insert offset: 0/0 key: issue-1 value: @@ -262,7 +262,7 @@ paths: title: Electric status: backlog - headers: - action: insert + operation: insert control: up-to-date offset: 1934/0 key: issue-2 diff --git a/docs/guides/quickstart.md b/docs/guides/quickstart.md index 9c8c7ac9ef..d0baac5359 100644 --- a/docs/guides/quickstart.md +++ b/docs/guides/quickstart.md @@ -120,18 +120,18 @@ x-electric-shape-id: 3833821-1721299734314 x-electric-chunk-last-offset: 0_0 etag: 3833821-1721299734314:-1:0_0 -[{"offset":"0_0","value":{"id":"1","name":"Alice","value":"3.14"},"key":"\"public\".\"foo\"/1","headers":{"action" +[{"offset":"0_0","value":{"id":"1","name":"Alice","value":"3.14"},"key":"\"public\".\"foo\"/1","headers":{"operation" :"insert"}},{"offset":"0_0","value":{"id":"2","name":"Bob","value":"2.71"},"key":"\"public\".\"foo\"/2","headers": -{"action":"insert"}},{"offset":"0_0","value":{"id":"3","name":"Charlie","value":"-1.618"},"key":"\"public\".\"foo\ -"/3","headers":{"action":"insert"}},{"offset":"0_0","value":{"id":"4","name":"David","value":"1.414"},"key":"\"pub -lic\".\"foo\"/4","headers":{"action":"insert"}},{"offset":"0_0","value":{"id":"5","name":"Eve","value":"0.0"},"key -":"\"public\".\"foo\"/5","headers":{"action":"insert"}},{"headers":{"control":"up-to-date"}}] +{"operation":"insert"}},{"offset":"0_0","value":{"id":"3","name":"Charlie","value":"-1.618"},"key":"\"public\".\"foo\ +"/3","headers":{"operation":"insert"}},{"offset":"0_0","value":{"id":"4","name":"David","value":"1.414"},"key":"\"pub +lic\".\"foo\"/4","headers":{"operation":"insert"}},{"offset":"0_0","value":{"id":"5","name":"Eve","value":"0.0"},"key +":"\"public\".\"foo\"/5","headers":{"operation":"insert"}},{"headers":{"control":"up-to-date"}}] ``` ::: info What are those messages in the response data? When you request shape data using the HTTP API you're actually requesting entries from a log of database operations affecting the data in the shape. This is called the [Shape Log](/api/http#shape-log). -The `offset` that you see in the messages and provide as the `?offset=...` query parameter in your request identifies a position in the log. The messages you see in the response are shape log entries (the ones with `value`s and `action` headers) and control messages (the ones with `control` headers). +The `offset` that you see in the messages and provide as the `?offset=...` query parameter in your request identifies a position in the log. The messages you see in the response are shape log entries (the ones with `value`s and `operation` headers) and control messages (the ones with `control` headers). ::: At this point, you could continue to fetch data using HTTP requests. However, let's switch up to fetch the same shape to use in a React app instead. diff --git a/examples/bash-client/bash-client.bash b/examples/bash-client/bash-client.bash index 318f7f6f03..e62af39fda 100755 --- a/examples/bash-client/bash-client.bash +++ b/examples/bash-client/bash-client.bash @@ -49,9 +49,9 @@ process_json() { # Read the JSON file line by line and save each JSON object to an individual file while IFS= read -r line; do - # Check if the headers array contains an object with key "action" - if echo "$line" | jq -e '.headers | map(select(.key == "action")) | length == 0' > /dev/null; then - # echo "Skipping line without an action: $action" # Log skipping non-data objects + # Check if the headers array contains an object with key "operation" + if echo "$line" | jq -e '.headers | map(select(.key == "operation")) | length == 0' > /dev/null; then + # echo "Skipping line without an operation: $operation" # Log skipping non-data objects continue fi diff --git a/examples/nextjs-example/app/match-stream.ts b/examples/nextjs-example/app/match-stream.ts index c43df53cc6..3c60edc9f3 100644 --- a/examples/nextjs-example/app/match-stream.ts +++ b/examples/nextjs-example/app/match-stream.ts @@ -20,10 +20,13 @@ export async function matchStream({ return new Promise((resolve, reject) => { const unsubscribe = stream.subscribe((messages) => { for (const message of messages) { - if (`key` in message && operations.includes(message.headers.action)) { + if ( + `key` in message && + operations.includes(message.headers.operation) + ) { if ( matchFn({ - operationType: message.headers.action, + operationType: message.headers.operation, message: message as ChangeMessage<{ [key: string]: T }>, }) ) { diff --git a/examples/remix-basic/app/match-stream.ts b/examples/remix-basic/app/match-stream.ts index 84fa28ee61..c670cf9481 100644 --- a/examples/remix-basic/app/match-stream.ts +++ b/examples/remix-basic/app/match-stream.ts @@ -20,8 +20,11 @@ export async function matchStream({ return new Promise((resolve, reject) => { const unsubscribe = stream.subscribe((messages) => { for (const message of messages) { - if (`key` in message && operations.includes(message.headers.action)) { - if (matchFn({ operationType: message.headers.action, message })) { + if ( + `key` in message && + operations.includes(message.headers.operation) + ) { + if (matchFn({ operationType: message.headers.operation, message })) { return finish(message) } } diff --git a/packages/react-hooks/test/support/test-helpers.ts b/packages/react-hooks/test/support/test-helpers.ts index 4fd09620aa..8c48f2f76b 100644 --- a/packages/react-hooks/test/support/test-helpers.ts +++ b/packages/react-hooks/test/support/test-helpers.ts @@ -36,7 +36,7 @@ export function forEachMessage( message as Message, messageIdx ) - if (`action` in message.headers) messageIdx++ + if (`operation` in message.headers) messageIdx++ } catch (e) { controller.abort() return reject(e) diff --git a/packages/sync-service/lib/electric/log_items.ex b/packages/sync-service/lib/electric/log_items.ex index 34935f8925..ea1afe54d6 100644 --- a/packages/sync-service/lib/electric/log_items.ex +++ b/packages/sync-service/lib/electric/log_items.ex @@ -29,7 +29,7 @@ defmodule Electric.LogItems do %{ key: change.key, value: change.record, - headers: %{action: :insert, txid: txid, relation: Tuple.to_list(change.relation)}, + headers: %{operation: :insert, txid: txid, relation: Tuple.to_list(change.relation)}, offset: change.log_offset } ] @@ -40,7 +40,7 @@ defmodule Electric.LogItems do %{ key: change.key, value: take_pks_or_all(change.old_record, pk_cols), - headers: %{action: :delete, txid: txid, relation: Tuple.to_list(change.relation)}, + headers: %{operation: :delete, txid: txid, relation: Tuple.to_list(change.relation)}, offset: change.log_offset } ] @@ -52,7 +52,7 @@ defmodule Electric.LogItems do %{ key: change.key, value: Map.take(change.record, Enum.concat(pk_cols, change.changed_columns)), - headers: %{action: :update, txid: txid, relation: Tuple.to_list(change.relation)}, + headers: %{operation: :update, txid: txid, relation: Tuple.to_list(change.relation)}, offset: change.log_offset } ] @@ -64,7 +64,7 @@ defmodule Electric.LogItems do key: change.old_key, value: take_pks_or_all(change.old_record, pk_cols), headers: %{ - action: :delete, + operation: :delete, txid: txid, relation: Tuple.to_list(change.relation), key_change_to: change.key @@ -75,7 +75,7 @@ defmodule Electric.LogItems do key: change.key, value: change.record, headers: %{ - action: :insert, + operation: :insert, txid: txid, relation: Tuple.to_list(change.relation), key_change_from: change.old_key @@ -106,7 +106,7 @@ defmodule Electric.LogItems do %{ key: key, value: value, - headers: %{action: :insert}, + headers: %{operation: :insert}, offset: offset } end diff --git a/packages/sync-service/test/electric/log_item_test.exs b/packages/sync-service/test/electric/log_item_test.exs index c20a2e0b28..2b3aefbdbc 100644 --- a/packages/sync-service/test/electric/log_item_test.exs +++ b/packages/sync-service/test/electric/log_item_test.exs @@ -19,7 +19,7 @@ defmodule Electric.LogItemsTest do offset: LogOffset.new(0, 0), value: %{"hello" => "world", "pk" => "10"}, key: "my_key", - headers: %{relation: ["public", "test"], action: :insert, txid: 1} + headers: %{relation: ["public", "test"], operation: :insert, txid: 1} } ] @@ -30,7 +30,7 @@ defmodule Electric.LogItemsTest do offset: LogOffset.new(0, 0), value: %{"hello" => "world", "pk" => "10"}, key: "my_key", - headers: %{relation: ["public", "test"], action: :insert, txid: 1} + headers: %{relation: ["public", "test"], operation: :insert, txid: 1} } ] end @@ -49,7 +49,7 @@ defmodule Electric.LogItemsTest do offset: LogOffset.new(0, 0), value: %{"pk" => "10"}, key: "my_key", - headers: %{relation: ["public", "test"], action: :delete, txid: 1} + headers: %{relation: ["public", "test"], operation: :delete, txid: 1} } ] end @@ -68,7 +68,7 @@ defmodule Electric.LogItemsTest do offset: LogOffset.new(0, 0), value: %{"hello" => "world", "value" => "10"}, key: "my_key", - headers: %{relation: ["public", "test"], action: :delete, txid: 1} + headers: %{relation: ["public", "test"], operation: :delete, txid: 1} } ] end @@ -89,7 +89,7 @@ defmodule Electric.LogItemsTest do offset: LogOffset.new(0, 0), value: %{"pk" => "10", "test" => "new"}, key: "my_key", - headers: %{relation: ["public", "test"], action: :update, txid: 1} + headers: %{relation: ["public", "test"], operation: :update, txid: 1} } ] end @@ -113,7 +113,7 @@ defmodule Electric.LogItemsTest do key: "old_key", headers: %{ relation: ["public", "test"], - action: :delete, + operation: :delete, txid: 1, key_change_to: "new_key" } @@ -124,7 +124,7 @@ defmodule Electric.LogItemsTest do key: "new_key", headers: %{ relation: ["public", "test"], - action: :insert, + operation: :insert, txid: 1, key_change_from: "old_key" } @@ -151,7 +151,7 @@ defmodule Electric.LogItemsTest do key: "old_key", headers: %{ relation: ["public", "test"], - action: :delete, + operation: :delete, txid: 1, key_change_to: "new_key" } @@ -162,7 +162,7 @@ defmodule Electric.LogItemsTest do key: "new_key", headers: %{ relation: ["public", "test"], - action: :insert, + operation: :insert, txid: 1, key_change_from: "old_key" } diff --git a/packages/sync-service/test/electric/plug/router_test.exs b/packages/sync-service/test/electric/plug/router_test.exs index 4fcc531c2b..f6fda8f0b0 100644 --- a/packages/sync-service/test/electric/plug/router_test.exs +++ b/packages/sync-service/test/electric/plug/router_test.exs @@ -55,7 +55,7 @@ defmodule Electric.Plug.RouterTest do assert [ %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "key" => _, "offset" => @first_offset, "value" => %{ @@ -159,7 +159,7 @@ defmodule Electric.Plug.RouterTest do assert [ %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "key" => ^key, "offset" => @first_offset, "value" => %{ @@ -198,7 +198,7 @@ defmodule Electric.Plug.RouterTest do assert [ %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "key" => ^key2, "offset" => _, "value" => %{ @@ -290,17 +290,17 @@ defmodule Electric.Plug.RouterTest do assert [ %{ - "headers" => %{"action" => "delete"}, + "headers" => %{"operation" => "delete"}, "value" => %{"id" => "1"}, "key" => ^key }, %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "value" => %{"id" => "2", "value1" => _, "value2" => _, "value3" => _}, "key" => key2 }, %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "value" => %{"id" => "3", "value1" => _, "value2" => _, "value3" => _}, "key" => key3 }, @@ -341,17 +341,17 @@ defmodule Electric.Plug.RouterTest do assert [ %{ - "headers" => %{"action" => "delete"}, + "headers" => %{"operation" => "delete"}, "value" => %{"col1" => "test1", "col2" => "test2"}, "key" => ^key }, %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "value" => %{"col1" => "test3", "col2" => "test2"}, "key" => key2 }, %{ - "headers" => %{"action" => "insert"}, + "headers" => %{"operation" => "insert"}, "value" => %{"col1" => "test4", "col2" => "test5"}, "key" => key3 }, diff --git a/packages/sync-service/test/electric/shape_cache/storage_implementations_test.exs b/packages/sync-service/test/electric/shape_cache/storage_implementations_test.exs index 41952e8ec6..7235b9e63f 100644 --- a/packages/sync-service/test/electric/shape_cache/storage_implementations_test.exs +++ b/packages/sync-service/test/electric/shape_cache/storage_implementations_test.exs @@ -92,13 +92,13 @@ defmodule Electric.ShapeCache.StorageImplimentationsTest do offset: @snapshot_offset_encoded, value: %{id: "00000000-0000-0000-0000-000000000001", title: "row1"}, key: ~S|"public"."the-table"/"00000000-0000-0000-0000-000000000001"|, - headers: %{action: "insert"} + headers: %{operation: "insert"} }, %{ offset: @snapshot_offset_encoded, value: %{id: "00000000-0000-0000-0000-000000000002", title: "row2"}, key: ~S|"public"."the-table"/"00000000-0000-0000-0000-000000000002"|, - headers: %{action: "insert"} + headers: %{operation: "insert"} } ] = Enum.map(stream, &Jason.decode!(&1, keys: :atoms)) end @@ -126,13 +126,13 @@ defmodule Electric.ShapeCache.StorageImplimentationsTest do offset: @snapshot_offset_encoded, value: %{id: "00000000-0000-0000-0000-000000000001", title: "row1"}, key: ~S|"public"."the-table"/"00000000-0000-0000-0000-000000000001"|, - headers: %{action: "insert"} + headers: %{operation: "insert"} }, %{ offset: @snapshot_offset_encoded, value: %{id: "00000000-0000-0000-0000-000000000002", title: "row2"}, key: ~S|"public"."the-table"/"00000000-0000-0000-0000-000000000002"|, - headers: %{action: "insert"} + headers: %{operation: "insert"} } ] = Enum.map(stream, &Jason.decode!(&1, keys: :atoms)) end @@ -192,7 +192,7 @@ defmodule Electric.ShapeCache.StorageImplimentationsTest do value: %{id: "123", name: "Test"}, offset: offset |> LogOffset.to_iolist() |> :erlang.iolist_to_binary(), headers: %{ - action: "insert", + operation: "insert", txid: 1, relation: ["public", "test_table"] } @@ -246,9 +246,9 @@ defmodule Electric.ShapeCache.StorageImplimentationsTest do entries = Enum.map(stream, &Jason.decode!(&1, keys: :atoms)) assert [ - %{headers: %{action: "insert"}}, - %{headers: %{action: "update"}}, - %{headers: %{action: "delete"}} + %{headers: %{operation: "insert"}}, + %{headers: %{operation: "update"}}, + %{headers: %{operation: "delete"}} ] = entries end @@ -291,8 +291,8 @@ defmodule Electric.ShapeCache.StorageImplimentationsTest do entries = Enum.map(stream, &Jason.decode!(&1, keys: :atoms)) assert [ - %{headers: %{action: "update"}}, - %{headers: %{action: "delete"}} + %{headers: %{operation: "update"}}, + %{headers: %{operation: "delete"}} ] = entries end @@ -342,7 +342,7 @@ defmodule Electric.ShapeCache.StorageImplimentationsTest do entries = Enum.map(stream, &Jason.decode!(&1, keys: :atoms)) - assert [%{headers: %{action: "update"}}] = entries + assert [%{headers: %{operation: "update"}}] = entries end test "returns only logs for the requested shape_id", %{module: storage, opts: opts} do diff --git a/packages/typescript-client/src/client.ts b/packages/typescript-client/src/client.ts index 6098c70f7e..221c23fcbd 100644 --- a/packages/typescript-client/src/client.ts +++ b/packages/typescript-client/src/client.ts @@ -516,10 +516,10 @@ export class Shape { messages.forEach((message) => { if (`key` in message) { dataMayHaveChanged = [`insert`, `update`, `delete`].includes( - message.headers.action + message.headers.operation ) - switch (message.headers.action) { + switch (message.headers.operation) { case `insert`: this.data.set(message.key, message.value) break diff --git a/packages/typescript-client/src/types.ts b/packages/typescript-client/src/types.ts index 02cfe7661e..191c34a2c7 100644 --- a/packages/typescript-client/src/types.ts +++ b/packages/typescript-client/src/types.ts @@ -20,7 +20,7 @@ export type ControlMessage = { export type ChangeMessage = { key: string value: T - headers: Header & { action: `insert` | `update` | `delete` } + headers: Header & { operation: `insert` | `update` | `delete` } offset: Offset } diff --git a/packages/typescript-client/test/support/test-helpers.ts b/packages/typescript-client/test/support/test-helpers.ts index 1335635473..afc0e83b54 100644 --- a/packages/typescript-client/test/support/test-helpers.ts +++ b/packages/typescript-client/test/support/test-helpers.ts @@ -37,7 +37,7 @@ export function forEachMessage( message as Message, messageIdx ) - if (`action` in message.headers) messageIdx++ + if (`operation` in message.headers) messageIdx++ } catch (e) { controller.abort() return reject(e)