From 5c33ad533545ff8791f462f42b2c3869b871ec7a Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:56:40 -0400 Subject: [PATCH 1/3] refactor(cli): build the SSE event body from flatten_row_event The HTTP /events serializer was the third hand-written flattening of RowEvent, repeating the action strings, the Update new_row/old_row split and the Error variant's nullable table and lossy path. It now consumes the core's flatten_row_event and adds only the transport's own shape: a presence check on the flat error that picks between the row keys and the error key. The wire body is unchanged byte for byte. serde_json's Map is a BTreeMap here, so the hand-built map renders in the same alphabetical key order as the json! literals it replaces. --- PARITY.md | 6 +- ...2026-09-18-sse-shares-flatten-row-event.md | 7 ++ packages/rust/src/cli/serialize.rs | 82 ++++++++----------- 3 files changed, 47 insertions(+), 48 deletions(-) create mode 100644 packages/rust/changelog.d/2026-09-18-sse-shares-flatten-row-event.md diff --git a/PARITY.md b/PARITY.md index da83b0d85..d88861bfc 100644 --- a/PARITY.md +++ b/PARITY.md @@ -18,8 +18,10 @@ lowercase `action` names — are language-neutral. Since [#1131](https://github.com/thekevinscott/dirsql/issues/1131) they live once in `dirsql::flatten_row_event` (`#[doc(hidden)]`, not a public Rust API); each binding keeps only the marshaling of the borrowed rows into host values -(`value_row_to_py_dict` / `value_row_to_js`) and its own event struct. No -SDK-visible shape changed — this restores parity rather than introducing drift. +(`value_row_to_py_dict` / `value_row_to_js`) and its own event struct. The +CLI's HTTP `/events` serializer shares that same source, so the SSE payload and +the two SDK event objects cannot drift apart. No SDK-visible shape changed — +this restores parity rather than introducing drift. The `on_file` callback receives a single argument: the absolute filesystem path of the matched file. `dirsql` does not read file contents — a callback diff --git a/packages/rust/changelog.d/2026-09-18-sse-shares-flatten-row-event.md b/packages/rust/changelog.d/2026-09-18-sse-shares-flatten-row-event.md new file mode 100644 index 000000000..7330ec41a --- /dev/null +++ b/packages/rust/changelog.d/2026-09-18-sse-shares-flatten-row-event.md @@ -0,0 +1,7 @@ +**Changed** + +The CLI's HTTP `/events` serializer now builds its JSON from +`dirsql::flatten_row_event` instead of its own per-variant `match`, leaving the +core as the single owner of the flattening decisions. The SSE payload is +unchanged byte for byte: same keys, same key order, same null-versus-absent +handling, same blob-to-hex rendering. diff --git a/packages/rust/src/cli/serialize.rs b/packages/rust/src/cli/serialize.rs index c487e8c31..0fe632c35 100644 --- a/packages/rust/src/cli/serialize.rs +++ b/packages/rust/src/cli/serialize.rs @@ -1,10 +1,11 @@ //! `Row` / `RowEvent` → JSON serialization for the HTTP API. -use serde_json::{Map, Value, json}; +use serde_json::{Map, Value}; use crate::Row; use crate::db::Value as CellValue; use crate::differ::RowEvent; +use crate::row_event_flat::flatten_row_event; pub(super) fn rows_to_json(rows: &[Row]) -> Vec { rows.iter().map(row_to_json).collect() @@ -35,52 +36,27 @@ pub(super) fn event_to_json(event: &RowEvent) -> String { } fn event_to_value(event: &RowEvent) -> Value { - match event { - RowEvent::Insert { - table, - row, - file_path, - } => json!({ - "action": "insert", - "table": table, - "file_path": file_path, - "row": row_to_json(row), - "old_row": Value::Null, - }), - RowEvent::Update { - table, - old_row, - new_row, - file_path, - } => json!({ - "action": "update", - "table": table, - "file_path": file_path, - "row": row_to_json(new_row), - "old_row": row_to_json(old_row), - }), - RowEvent::Delete { - table, - row, - file_path, - } => json!({ - "action": "delete", - "table": table, - "file_path": file_path, - "row": row_to_json(row), - "old_row": Value::Null, - }), - RowEvent::Error { - table, - file_path, - error, - } => json!({ - "action": "error", - "table": table, - "file_path": file_path.to_string_lossy(), - "error": error, - }), + let flat = flatten_row_event(event); + let mut map = Map::new(); + map.insert("action".into(), Value::from(flat.action)); + map.insert( + "table".into(), + flat.table.map_or(Value::Null, Value::String), + ); + map.insert("file_path".into(), Value::String(flat.file_path)); + match flat.error { + Some(error) => { + map.insert("error".into(), Value::String(error)); + } + None => { + map.insert("row".into(), flat.row.map_or(Value::Null, row_to_json)); + map.insert( + "old_row".into(), + flat.old_row.map_or(Value::Null, row_to_json), + ); + } } + Value::Object(map) } #[cfg(test)] @@ -116,6 +92,7 @@ mod tests { Some("posts/a.json"), ); assert!(parsed.get("old_row").unwrap().is_null()); + assert!(parsed.get("error").is_none()); assert_eq!(event_to_json(&event), parsed.to_string()); } @@ -157,6 +134,19 @@ mod tests { ); } + #[test] + fn error_event_omits_the_row_keys() { + let event = RowEvent::Error { + table: None, + file_path: PathBuf::from("bad.json"), + error: "parse failed".into(), + }; + let parsed = event_to_value(&event); + assert!(parsed.get("table").unwrap().is_null()); + assert!(parsed.get("row").is_none()); + assert!(parsed.get("old_row").is_none()); + } + #[test] fn delete_event_emits_expected_shape() { let mut row: Row = HashMap::new(); From 7d0bce94d84c7e51ec1e6a2bf005b4a265624456 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:57:51 -0400 Subject: [PATCH 2/3] e2e attestation for claude/1139-sse-flatten --- .../python/e2e-attestations/claude-1139-sse-flatten.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/python/e2e-attestations/claude-1139-sse-flatten.json diff --git a/packages/python/e2e-attestations/claude-1139-sse-flatten.json b/packages/python/e2e-attestations/claude-1139-sse-flatten.json new file mode 100644 index 000000000..761f10abd --- /dev/null +++ b/packages/python/e2e-attestations/claude-1139-sse-flatten.json @@ -0,0 +1,7 @@ +{ + "command": "just test-e2e", + "ran_at": 1789750671, + "exit_code": 0, + "commit": "5c33ad533545ff8791f462f42b2c3869b871ec7a", + "branch": "claude/1139-sse-flatten" +} From e49a9a904aaac23f436da16ed0a991a0d6cd8534 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:58:48 -0400 Subject: [PATCH 3/3] e2e attestation for claude/1139-sse-flatten --- packages/ts/e2e-attestations/claude-1139-sse-flatten.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/ts/e2e-attestations/claude-1139-sse-flatten.json diff --git a/packages/ts/e2e-attestations/claude-1139-sse-flatten.json b/packages/ts/e2e-attestations/claude-1139-sse-flatten.json new file mode 100644 index 000000000..88623fddf --- /dev/null +++ b/packages/ts/e2e-attestations/claude-1139-sse-flatten.json @@ -0,0 +1,7 @@ +{ + "command": "pnpm test:e2e", + "ran_at": 1789750728, + "exit_code": 0, + "commit": "7d0bce94d84c7e51ec1e6a2bf005b4a265624456", + "branch": "claude/1139-sse-flatten" +}