From f553a51795c1d0c5ddf697e0475bd675ce31550f Mon Sep 17 00:00:00 2001 From: jrtxio Date: Fri, 23 Oct 2026 12:00:39 +0800 Subject: [PATCH 1/5] codegen: emit the Kotlin client only for projects with a companion API (#311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every project got a Kotlin client emitted into .rivet/generated/ — a tree section the standard .gitignore excludes (rivet's own example layout ignores it). The release freshness gate diffs the tree against client-outputs, so every desktop-only product failed the gate with 1 generated file missing until it force-added a file nothing consumes; examples/taskboard fails its own gate on a fresh clone. The Kotlin client is now part of the fresh output set only when the project declares companion device RPCs — exactly the projects with a Kotlin consumer. client-outputs is exported so products and tests can inspect the fresh set directly. examples/taskboard is regenerated against current main (it declares device-rpcs and keeps its client). --- examples/taskboard/linux/GeneratedBackend.hpp | 17 +++++++++++ .../taskboard/windows/GeneratedBackend.hpp | 17 +++++++++++ rivet-cli/codegen.rkt | 29 +++++++++++++------ rivet/tests/device-codegen.rkt | 26 +++++++++++++---- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/examples/taskboard/linux/GeneratedBackend.hpp b/examples/taskboard/linux/GeneratedBackend.hpp index 69e7028c..32f87431 100644 --- a/examples/taskboard/linux/GeneratedBackend.hpp +++ b/examples/taskboard/linux/GeneratedBackend.hpp @@ -1,6 +1,7 @@ // Generated by Rivet. Do not edit by hand. #pragma once +#include #include #include #include @@ -148,36 +149,52 @@ class API { std::future create_task(std::string title, std::string notes) { auto rivet_call = backend_.call("create-task", rivet::Value::List{detail::encode_String(title), detail::encode_String(notes)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> BoardTask { return detail::decode_BoardTask(rivet_call.get()); }); } [[nodiscard]] std::uint64_t create_task_async(std::string title, std::string notes, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("create-task", rivet::Value::List{detail::encode_String(title), detail::encode_String(notes)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_BoardTask(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional create_task_within(std::string title, std::string notes, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("create-task", rivet::Value::List{detail::encode_String(title), detail::encode_String(notes)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_BoardTask(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future delete_task(std::int64_t id) { auto rivet_call = backend_.call("delete-task", rivet::Value::List{detail::encode_Int64(id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t delete_task_async(std::int64_t id, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("delete-task", rivet::Value::List{detail::encode_Int64(id)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional delete_task_within(std::int64_t id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("delete-task", rivet::Value::List{detail::encode_Int64(id)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> generate_demo_tasks(std::int64_t count) { auto rivet_call = backend_.call("generate-demo-tasks", rivet::Value::List{detail::encode_Int64(count)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t generate_demo_tasks_async(std::int64_t count, std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("generate-demo-tasks", rivet::Value::List{detail::encode_Int64(count)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> generate_demo_tasks_within(std::int64_t count, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("generate-demo-tasks", rivet::Value::List{detail::encode_Int64(count)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> get_task(std::int64_t id) { auto rivet_call = backend_.call("get-task", rivet::Value::List{detail::encode_Int64(id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::optional { return detail::decode__Optional_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t get_task_async(std::int64_t id, std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("get-task", rivet::Value::List{detail::encode_Int64(id)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__Optional_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> get_task_within(std::int64_t id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("get-task", rivet::Value::List{detail::encode_Int64(id)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__Optional_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> list_tasks() { auto rivet_call = backend_.call("list-tasks", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t list_tasks_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("list-tasks", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> list_tasks_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("list-tasks", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> reload_sample_tasks() { auto rivet_call = backend_.call("reload-sample-tasks", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t reload_sample_tasks_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("reload-sample-tasks", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> reload_sample_tasks_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("reload-sample-tasks", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> select_task(std::int64_t id) { auto rivet_call = backend_.call("select-task", rivet::Value::List{detail::encode_Int64(id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::optional { return detail::decode__Optional_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t select_task_async(std::int64_t id, std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("select-task", rivet::Value::List{detail::encode_Int64(id)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__Optional_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> select_task_within(std::int64_t id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("select-task", rivet::Value::List{detail::encode_Int64(id)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__Optional_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> timing_decoration() { auto rivet_call = backend_.call("timing-decoration", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_TimingThreshold_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_decoration_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-decoration", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_TimingThreshold_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> timing_decoration_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-decoration", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_TimingThreshold_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_is_running() { auto rivet_call = backend_.call("timing-is-running", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_is_running_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-is-running", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_is_running_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-is-running", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> timing_markers_all() { auto rivet_call = backend_.call("timing-markers-all", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_TimingMarker_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_markers_all_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-markers-all", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_TimingMarker_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> timing_markers_all_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-markers-all", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_TimingMarker_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> timing_series_specs() { auto rivet_call = backend_.call("timing-series-specs", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_TimingSeries_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_series_specs_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-series-specs", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_TimingSeries_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> timing_series_specs_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-series-specs", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_TimingSeries_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_set_window(std::int64_t span_ms) { auto rivet_call = backend_.call("timing-set-window", rivet::Value::List{detail::encode_Int64(span_ms)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_set_window_async(std::int64_t span_ms, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-set-window", rivet::Value::List{detail::encode_Int64(span_ms)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_set_window_within(std::int64_t span_ms, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-set-window", rivet::Value::List{detail::encode_Int64(span_ms)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_start() { auto rivet_call = backend_.call("timing-start", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_start_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-start", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_start_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-start", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_stop() { auto rivet_call = backend_.call("timing-stop", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_stop_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-stop", rivet::Value::List{}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_stop_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-stop", rivet::Value::List{}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_window(std::string series_id) { auto rivet_call = backend_.call("timing-window", rivet::Value::List{detail::encode_String(series_id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> TimingWindow { return detail::decode_TimingWindow(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_window_async(std::string series_id, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-window", rivet::Value::List{detail::encode_String(series_id)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_TimingWindow(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_window_within(std::string series_id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-window", rivet::Value::List{detail::encode_String(series_id)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_TimingWindow(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future update_task(std::int64_t id, std::string title, std::string notes, TaskStatus status) { auto rivet_call = backend_.call("update-task", rivet::Value::List{detail::encode_Int64(id), detail::encode_String(title), detail::encode_String(notes), detail::encode_TaskStatus(status)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> BoardTask { return detail::decode_BoardTask(rivet_call.get()); }); } [[nodiscard]] std::uint64_t update_task_async(std::int64_t id, std::string title, std::string notes, TaskStatus status, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("update-task", rivet::Value::List{detail::encode_Int64(id), detail::encode_String(title), detail::encode_String(notes), detail::encode_TaskStatus(status)}, [completion = std::move(completion)](rivet::linux_runtime::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_BoardTask(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional update_task_within(std::int64_t id, std::string title, std::string notes, TaskStatus status, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("update-task", rivet::Value::List{detail::encode_Int64(id), detail::encode_String(title), detail::encode_String(notes), detail::encode_TaskStatus(status)}, [rivet_done](rivet::linux_runtime::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_BoardTask(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } // Shared state std::future> get_selected_task_id() { auto rivet_call = backend_.get_state("selected-task-id"); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::optional { return detail::decode__Optional_Int64_(rivet_call.get()); }); } diff --git a/examples/taskboard/windows/GeneratedBackend.hpp b/examples/taskboard/windows/GeneratedBackend.hpp index 467538af..65bdc906 100644 --- a/examples/taskboard/windows/GeneratedBackend.hpp +++ b/examples/taskboard/windows/GeneratedBackend.hpp @@ -1,6 +1,7 @@ // Generated by Rivet. Do not edit by hand. #pragma once +#include #include #include #include @@ -148,36 +149,52 @@ class API { std::future create_task(std::string title, std::string notes) { auto rivet_call = backend_.call("create-task", rivet::Value::List{detail::encode_String(title), detail::encode_String(notes)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> BoardTask { return detail::decode_BoardTask(rivet_call.get()); }); } [[nodiscard]] std::uint64_t create_task_async(std::string title, std::string notes, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("create-task", rivet::Value::List{detail::encode_String(title), detail::encode_String(notes)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_BoardTask(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional create_task_within(std::string title, std::string notes, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("create-task", rivet::Value::List{detail::encode_String(title), detail::encode_String(notes)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_BoardTask(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future delete_task(std::int64_t id) { auto rivet_call = backend_.call("delete-task", rivet::Value::List{detail::encode_Int64(id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t delete_task_async(std::int64_t id, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("delete-task", rivet::Value::List{detail::encode_Int64(id)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional delete_task_within(std::int64_t id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("delete-task", rivet::Value::List{detail::encode_Int64(id)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> generate_demo_tasks(std::int64_t count) { auto rivet_call = backend_.call("generate-demo-tasks", rivet::Value::List{detail::encode_Int64(count)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t generate_demo_tasks_async(std::int64_t count, std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("generate-demo-tasks", rivet::Value::List{detail::encode_Int64(count)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> generate_demo_tasks_within(std::int64_t count, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("generate-demo-tasks", rivet::Value::List{detail::encode_Int64(count)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> get_task(std::int64_t id) { auto rivet_call = backend_.call("get-task", rivet::Value::List{detail::encode_Int64(id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::optional { return detail::decode__Optional_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t get_task_async(std::int64_t id, std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("get-task", rivet::Value::List{detail::encode_Int64(id)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__Optional_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> get_task_within(std::int64_t id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("get-task", rivet::Value::List{detail::encode_Int64(id)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__Optional_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> list_tasks() { auto rivet_call = backend_.call("list-tasks", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t list_tasks_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("list-tasks", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> list_tasks_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("list-tasks", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> reload_sample_tasks() { auto rivet_call = backend_.call("reload-sample-tasks", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t reload_sample_tasks_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("reload-sample-tasks", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> reload_sample_tasks_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("reload-sample-tasks", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> select_task(std::int64_t id) { auto rivet_call = backend_.call("select-task", rivet::Value::List{detail::encode_Int64(id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::optional { return detail::decode__Optional_BoardTask_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t select_task_async(std::int64_t id, std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("select-task", rivet::Value::List{detail::encode_Int64(id)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__Optional_BoardTask_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> select_task_within(std::int64_t id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("select-task", rivet::Value::List{detail::encode_Int64(id)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__Optional_BoardTask_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> timing_decoration() { auto rivet_call = backend_.call("timing-decoration", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_TimingThreshold_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_decoration_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-decoration", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_TimingThreshold_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> timing_decoration_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-decoration", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_TimingThreshold_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_is_running() { auto rivet_call = backend_.call("timing-is-running", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_is_running_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-is-running", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_is_running_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-is-running", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> timing_markers_all() { auto rivet_call = backend_.call("timing-markers-all", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_TimingMarker_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_markers_all_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-markers-all", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_TimingMarker_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> timing_markers_all_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-markers-all", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_TimingMarker_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future> timing_series_specs() { auto rivet_call = backend_.call("timing-series-specs", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::vector { return detail::decode__List_TimingSeries_(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_series_specs_async(std::function>)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-series-specs", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result> result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode__List_TimingSeries_(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional> timing_series_specs_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-series-specs", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode__List_TimingSeries_(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_set_window(std::int64_t span_ms) { auto rivet_call = backend_.call("timing-set-window", rivet::Value::List{detail::encode_Int64(span_ms)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_set_window_async(std::int64_t span_ms, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-set-window", rivet::Value::List{detail::encode_Int64(span_ms)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_set_window_within(std::int64_t span_ms, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-set-window", rivet::Value::List{detail::encode_Int64(span_ms)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_start() { auto rivet_call = backend_.call("timing-start", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_start_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-start", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_start_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-start", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_stop() { auto rivet_call = backend_.call("timing-stop", rivet::Value::List{}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> bool { return detail::decode_Bool(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_stop_async(std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-stop", rivet::Value::List{}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_Bool(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_stop_within(std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-stop", rivet::Value::List{}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_Bool(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future timing_window(std::string series_id) { auto rivet_call = backend_.call("timing-window", rivet::Value::List{detail::encode_String(series_id)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> TimingWindow { return detail::decode_TimingWindow(rivet_call.get()); }); } [[nodiscard]] std::uint64_t timing_window_async(std::string series_id, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("timing-window", rivet::Value::List{detail::encode_String(series_id)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_TimingWindow(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional timing_window_within(std::string series_id, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("timing-window", rivet::Value::List{detail::encode_String(series_id)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_TimingWindow(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } std::future update_task(std::int64_t id, std::string title, std::string notes, TaskStatus status) { auto rivet_call = backend_.call("update-task", rivet::Value::List{detail::encode_Int64(id), detail::encode_String(title), detail::encode_String(notes), detail::encode_TaskStatus(status)}); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> BoardTask { return detail::decode_BoardTask(rivet_call.get()); }); } [[nodiscard]] std::uint64_t update_task_async(std::int64_t id, std::string title, std::string notes, TaskStatus status, std::function)> completion) { if (!completion) throw std::invalid_argument("Rivet async completion handler is empty"); return backend_.request_async("update-task", rivet::Value::List{detail::encode_Int64(id), detail::encode_String(title), detail::encode_String(notes), detail::encode_TaskStatus(status)}, [completion = std::move(completion)](rivet::windows::CallResult rivet_result) mutable { Result result; if (rivet_result.error) { result.error = rivet_result.error; } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); result.value = detail::decode_BoardTask(*rivet_result.value); } catch (...) { result.error = std::current_exception(); } } completion(std::move(result)); }); } + std::optional update_task_within(std::int64_t id, std::string title, std::string notes, TaskStatus status, std::chrono::milliseconds rivet_timeout) { auto rivet_done = std::make_shared>(); auto rivet_future = rivet_done->get_future(); auto rivet_id = backend_.request_async("update-task", rivet::Value::List{detail::encode_Int64(id), detail::encode_String(title), detail::encode_String(notes), detail::encode_TaskStatus(status)}, [rivet_done](rivet::windows::CallResult rivet_result) mutable { if (rivet_result.error) { rivet_done->set_exception(rivet_result.error); } else { try { if (!rivet_result.value) throw std::runtime_error("Rivet async call completed without a value"); rivet_done->set_value(detail::decode_BoardTask(*rivet_result.value)); } catch (...) { rivet_done->set_exception(std::current_exception()); } } }); if (rivet_future.wait_for(rivet_timeout) != std::future_status::ready) { backend_.cancel(rivet_id); return std::nullopt; } return rivet_future.get(); } // Shared state std::future> get_selected_task_id() { auto rivet_call = backend_.get_state("selected-task-id"); return std::async(std::launch::deferred, [rivet_call = std::move(rivet_call)]() mutable -> std::optional { return detail::decode__Optional_Int64_(rivet_call.get()); }); } diff --git a/rivet-cli/codegen.rkt b/rivet-cli/codegen.rkt index e2933774..c4064e57 100644 --- a/rivet-cli/codegen.rkt +++ b/rivet-cli/codegen.rkt @@ -11,6 +11,7 @@ "codegen/type-graph.rkt") (provide generate-clients! + client-outputs check-generated-clients schema-snapshot write-schema-snapshot! @@ -52,8 +53,25 @@ (define codable-types (device-codable-named-types device-rpcs)) (void schema-types) (validate-device-rpcs! device-rpcs) + ;; Android consumes the typed client from the shared generated tree + ;; until generated Compose projects exist; the package layout keeps + ;; the file drop-in for Gradle source sets. Only projects declaring a + ;; companion-device API get the Kotlin client: a desktop-only product + ;; has no consumer for it, and a file nobody consumes must not be a + ;; required part of the fresh tree the release gate diffs against — + ;; `.rivet/` is gitignored in the standard layout, so the + ;; always-emitted Kotlin client failed every desktop-only product's + ;; freshness check (issue #311). + (define pairs-with-kotlin + (if (null? device-rpcs) + '() + (list (cons (project-path project ".rivet" "generated" "kotlin" "dev" "rivet" + "generated" "GeneratedBackend.kt") + (generate-kotlin rpcs events states records enums device-rpcs + module-name entry-name display-name version build + identifier release-channel))))) (define pairs - (list + (list* (cons (project-path project "macos-host" "Sources" "RivetHost" "GeneratedBackend.swift") (parameterize ([current-swift-codable-types codable-types]) (generate-swift rpcs events states records enums device-rpcs @@ -63,14 +81,7 @@ (generate-cpp rpcs events states records enums module-name entry-name display-name version build identifier release-channel "rivet::windows")) - ;; Android consumes the typed client from the shared generated tree - ;; until generated Compose projects exist; the package layout keeps - ;; the file drop-in for Gradle source sets. - (cons (project-path project ".rivet" "generated" "kotlin" "dev" "rivet" - "generated" "GeneratedBackend.kt") - (generate-kotlin rpcs events states records enums device-rpcs - module-name entry-name display-name version build - identifier release-channel)))) + pairs-with-kotlin)) (define linux-host (project-path project "linux")) (if (directory-exists? linux-host) (cons (cons (build-path linux-host "GeneratedBackend.hpp") diff --git a/rivet/tests/device-codegen.rkt b/rivet/tests/device-codegen.rkt index 773f7aed..d7c91971 100644 --- a/rivet/tests/device-codegen.rkt +++ b/rivet/tests/device-codegen.rkt @@ -56,11 +56,22 @@ RKT out))) + ;; Issue #311: with no declared companion-device API the project has no + ;; Kotlin consumer, so the fresh output set must not require the Kotlin + ;; client — desktop-only products keep `.rivet/` gitignored and must + ;; still pass the release freshness gate. + (check-false + (for/or ([pair (in-list (car (client-outputs (load-project project-root))))]) + (string-contains? (path->string (car pair)) "GeneratedBackend.kt"))) + ;; Companion APIs are default-deny and only the configured RPCs appear in ;; the generated Codable request/client/router surface. (set-device-rpcs! '(fetch-profile refresh)) (define project (load-project project-root)) (generate-clients! project) + (check-true + (for/or ([pair (in-list (car (client-outputs project)))]) + (string-contains? (path->string (car pair)) "GeneratedBackend.kt"))) (define swift (file->string (build-path project-root "macos-host" "Sources" "RivetHost" @@ -103,12 +114,15 @@ RKT (define exported-baseline (build-path project-root ".rivet" "device-exported.json")) (write-schema-snapshot! project exported-baseline) (set-device-rpcs! '()) - (generate-clients! (load-project project-root)) - (define no-device-kotlin - (file->string - (build-path project-root ".rivet" "generated" "kotlin" "dev" "rivet" - "generated" "GeneratedBackend.kt"))) - (check-false (string-contains? no-device-kotlin "class RivetDeviceAPI")) + ;; Issue #311: an empty companion API means no Kotlin consumer — the + ;; fresh output set drops the Kotlin client entirely. A previously + ;; generated file may linger on disk, but it is no longer part of the + ;; contract the release freshness gate diffs against. + (define empty-device-project (load-project project-root)) + (check-false + (for/or ([pair (in-list (car (client-outputs empty-device-project)))]) + (string-contains? (path->string (car pair)) "GeneratedBackend.kt"))) + (generate-clients! empty-device-project) (define removal-report (check-schema-compatibility! (load-project project-root) exported-baseline)) (check-false (hash-ref removal-report 'compatible)) From 983831d07d6545482faced5a4ee513b25003f1c0 Mon Sep 17 00:00:00 2001 From: jrtxio Date: Fri, 23 Oct 2026 12:22:35 +0800 Subject: [PATCH 2/5] codegen tests: cover the gated Kotlin client without assuming its file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The demo project has no companion API, so the fresh set no longer contains the Kotlin client: assert its absence, declare a companion RPC around the Kotlin content and enum content sections (which also switch Swift into the RivetTypes device surface — the flip sits after the Swift/C++ assertions), and restore the desktop-only manifest before the sections that reuse the project binding. --- rivet/tests/codegen.rkt | 51 ++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/rivet/tests/codegen.rkt b/rivet/tests/codegen.rkt index f2ad86de..066da69c 100644 --- a/rivet/tests/codegen.rkt +++ b/rivet/tests/codegen.rkt @@ -164,13 +164,21 @@ RKT (define stale-report (check-generated-clients project)) (check-equal? (hash-ref stale-report 'stale) (list windows-header)) + ;; Issue #311: the demo project declares no companion device RPCs, so + ;; the Kotlin client is not part of the fresh output set at all — + ;; desktop-only products keep `.rivet/` gitignored and still pass the + ;; gate without committing a file nothing consumes. (define kotlin-generated (build-path project-root ".rivet" "generated" "kotlin" "dev" "rivet" "generated" "GeneratedBackend.kt")) - (delete-file kotlin-generated) + (check-false + (for/or ([pair (in-list (car (client-outputs project)))]) + (string=? (path->string (car pair)) (path->string kotlin-generated)))) + + (delete-file windows-header) (define missing-report (check-generated-clients project)) - (check-equal? (hash-ref missing-report 'missing) (list kotlin-generated)) - (check-equal? (hash-ref missing-report 'stale) (list windows-header)) + (check-equal? (hash-ref missing-report 'missing) (list windows-header)) + (check-equal? (hash-ref missing-report 'stale) '()) (void (generate-clients! project)) (define repaired-report (check-generated-clients project)) @@ -213,11 +221,6 @@ RKT (define linux-cpp (file->string (build-path project-root "linux" "GeneratedBackend.hpp"))) - (define kotlin - (file->string - (build-path project-root - ".rivet" "generated" "kotlin" "dev" "rivet" "generated" - "GeneratedBackend.kt"))) (check-true (string-contains? swift "public protocol RivetGeneratedRPCClient: Sendable")) (check-true (string-contains? swift "extension RivetClient: RivetGeneratedRPCClient {}")) @@ -272,6 +275,21 @@ RKT (regexp-match? #rx"rivet::linux_runtime::CallResult rivet_result" linux-cpp)) (check-false (regexp-match? #rx"rivet::windows" linux-cpp)) + ;; Issue #311: declare a companion API so the Kotlin client joins the + ;; fresh output set, then generate and assert its content. (The fresh-set + ;; assertion above proves a desktop-only project omits the file.) + (define config-path (build-path project-root "rivet.rktd")) + (define base-config (call-with-input-file config-path read)) + (call-with-output-file config-path + #:exists 'truncate + (lambda (out) (write (hash-set base-config 'device-rpcs '(greet)) out))) + (set! project (load-project project-root)) + (generate-clients! project) + (define kotlin + (file->string + (build-path project-root + ".rivet" "generated" "kotlin" "dev" "rivet" "generated" + "GeneratedBackend.kt"))) ;; The typed Kotlin client targets the coroutine runtime. State accessors ;; are package-level extension functions, so the imports are load-bearing. (check-true (regexp-match? #rx"package dev.rivet.generated" kotlin)) @@ -309,6 +327,13 @@ RKT (check-true (regexp-match? #rx"\"progress\" -> Progress\\(decode_Int64\\(value\\)\\)" kotlin)) + ;; Restore the desktop-only manifest: the remaining sections reuse the + ;; same project binding with backends that declare no `greet`. + (call-with-output-file config-path + #:exists 'truncate + (lambda (out) (write (hash-set base-config 'device-rpcs '()) out))) + (set! project (load-project project-root)) + ;; A versioned snapshot is suitable for source control and CI. Additions are ;; compatible, while changing a published signature is reported and fails. (define baseline-path (build-path project-root "schema-baseline.json")) @@ -458,6 +483,13 @@ RKT (check-regexp-match #rx"func echo_role\\(role: RivetTypes.Role\\) async throws -> RivetTypes.Role" enum-swift) (check-regexp-match #rx"enum class Role \\{ admin, member \\};" enum-cpp) (check-regexp-match #rx"std::future echo_role\\(Role role\\)" enum-cpp) + ;; The Kotlin checks need a companion API for the client to exist at all + ;; (issue #311); declaring one also switches Swift into the RivetTypes + ;; device surface, so this flip happens after the Swift/C++ assertions. + (call-with-output-file config-path #:exists 'truncate + (lambda (out) (write (hash-set base-config 'device-rpcs '(echo-role)) out))) + (set! project (load-project project-root)) + (generate-clients! project) (define enum-kotlin (file->string (build-path project-root @@ -467,6 +499,9 @@ RKT (check-regexp-match #rx"admin\\(\"admin\"\\)," enum-kotlin) (check-regexp-match #rx"suspend fun echo_role\\(role: Role\\): Role" enum-kotlin) (check-regexp-match #rx"Role\\.fromWireName" enum-kotlin) + (call-with-output-file config-path #:exists 'truncate + (lambda (out) (write (hash-set base-config 'device-rpcs '()) out))) + (set! project (load-project project-root)) (define enum-baseline-path (build-path project-root "enum-schema.json")) (write-schema-snapshot! project enum-baseline-path) From 12bf1ab5e3002f106a639ed23019dcca74f11f9a Mon Sep 17 00:00:00 2001 From: jrtxio Date: Fri, 23 Oct 2026 12:36:04 +0800 Subject: [PATCH 3/5] generate: report the honest client set; smoke declares a companion API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'generated Swift, C++, and Kotlin clients' line was static; now it reflects whether the project's fresh output set includes the Kotlin client. The source-package smoke asserts the Kotlin client exists after declaring the starter's greet RPC — a desktop-only scaffold no longer carries the file (issue #311). --- .github/scripts/smoke-source-package.sh | 7 +++++-- rivet-cli/main.rkt | 11 +++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/scripts/smoke-source-package.sh b/.github/scripts/smoke-source-package.sh index 2a1c2536..270d2c12 100644 --- a/.github/scripts/smoke-source-package.sh +++ b/.github/scripts/smoke-source-package.sh @@ -149,8 +149,11 @@ PLTUSERHOME="$user_home" racket -e ' "generated-clients.kotlin.path") ' -# The published archive must still generate every typed client, including the -# Kotlin client that Android applications consume from the shared tree. +# The published archive must still generate every typed client. The Kotlin +# client exists only for projects with a declared companion API (issue #311 — +# a desktop-only scaffold carries no Kotlin file nothing consumes), so declare +# the starter's `greet` RPC before generating and assert the file appears. +racket -e '(require racket/file) (define p "rivet.rktd") (define c (call-with-input-file p read)) (call-with-output-file p #:exists (quote truncate/replace) (lambda (out) (write (hash-set c (quote device-rpcs) (list (quote greet))) out)))' PLTUSERHOME="$user_home" \ raco rivet generate test -s .rivet/generated/kotlin/dev/rivet/generated/GeneratedBackend.kt diff --git a/rivet-cli/main.rkt b/rivet-cli/main.rkt index a32f2b42..64f9926e 100644 --- a/rivet-cli/main.rkt +++ b/rivet-cli/main.rkt @@ -174,8 +174,15 @@ (printf " ~a\n" path))))] [(list "generate") (define generate-clients! (command "codegen" 'generate-clients!)) - (generate-clients! (current-project!)) - (say "generated Swift, C++, and Kotlin clients")] + (define client-outputs (command "codegen" 'client-outputs)) + (define project (current-project!)) + (define outputs (car (client-outputs project))) + (generate-clients! project) + (define kotlin? (for/or ([pair (in-list outputs)]) + (regexp-match? #rx"[.]kt$" (path->string (car pair))))) + (say (if kotlin? + "generated Swift, C++, and Kotlin clients" + "generated Swift and C++ clients (no companion API: no Kotlin client)"))] [(list "generate" "--check") ;; CI freshness gate: regenerate in memory and compare with the tree, ;; so hand-edited generated files or stale version stamps cannot merge. From d2848c4bfb7da04662615d08257f567d67841d9e Mon Sep 17 00:00:00 2001 From: jrtxio Date: Fri, 23 Oct 2026 12:47:11 +0800 Subject: [PATCH 4/5] ci: declare the companion API for the Kotlin schema-matrix smoke The KtSmoke scaffold is the Android consumer of the shared generated client, so it declares the device RPCs it exercises before generating (issue #311). --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0ef0c53..57ac9c4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -261,6 +261,9 @@ jobs: raco rivet new KtSmoke cd KtSmoke cp "$GITHUB_WORKSPACE/rivet/tests/fixtures/schema-matrix-backend.rkt" app/backend.rkt + # The Kotlin client exists only for projects with a declared + # companion API (issue #311); this smoke is the Android consumer. + racket -e '(require racket/file) (define p "rivet.rktd") (define c (call-with-input-file p read)) (call-with-output-file p #:exists (quote truncate/replace) (lambda (out) (write (hash-set c (quote device-rpcs) (list (quote greet) (quote increment))) out)))' raco rivet schema --output rivet-schema.json raco rivet schema check rivet-schema.json --json raco rivet generate From b84cfd65e0ad35d52466ee7d585e5d9b63326935 Mon Sep 17 00:00:00 2001 From: jrtxio Date: Fri, 23 Oct 2026 12:47:34 +0800 Subject: [PATCH 5/5] docs: the Kotlin client ships for projects with a declared companion API --- README.md | 2 +- README.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c89e869..b901dedf 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ The Racket backend declares the API shared by all three native hosts: (serve-fds in-fd out-fd)) ``` -`raco rivet build` reads the RPC, Event, State, Record, and Enum schema and generates typed native clients. Swift gets methods such as `increment(value:)`, `get_counter()`, and `set_counter(_:)`; C++ gets their native equivalents; Kotlin gets suspend methods such as `increment(value)` in `.rivet/generated/kotlin/dev/rivet/generated/GeneratedBackend.kt`. +`raco rivet build` reads the RPC, Event, State, Record, and Enum schema and generates typed native clients. Swift gets methods such as `increment(value:)`, `get_counter()`, and `set_counter(_:)`; C++ gets their native equivalents; Kotlin gets the same surface in `.rivet/generated/kotlin/dev/rivet/generated/GeneratedBackend.kt` for projects that declare companion `device-rpcs` (a desktop-only project has no Kotlin consumer, so the file is not part of its fresh output set). ### How it compares diff --git a/README.zh-CN.md b/README.zh-CN.md index 0eea682d..b8a053c7 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -75,7 +75,7 @@ Rivet 刻意不做 WebView 框架,也不做统一跨平台控件层。Windows (serve-fds in-fd out-fd)) ``` -`raco rivet build` 会读取 RPC、Event、State、Record 与 Enum schema,并生成类型化原生客户端。Swift 侧得到 `increment(value:)`、`get_counter()`、`set_counter(_:)` 等方法;C++ 侧得到对应的原生接口;Kotlin 侧在 `.rivet/generated/kotlin/dev/rivet/generated/GeneratedBackend.kt` 得到 `increment(value)` 等 suspend 方法。 +`raco rivet build` 会读取 RPC、Event、State、Record 与 Enum schema,并生成类型化原生客户端。Swift 侧得到 `increment(value:)`、`get_counter()`、`set_counter(_:)` 等方法;C++ 侧得到对应的原生接口;Kotlin 侧在 `.rivet/generated/kotlin/dev/rivet/generated/GeneratedBackend.kt` 得到同样的接口——仅限声明了 companion `device-rpcs` 的项目(纯桌面项目没有 Kotlin 消费方,该文件不在其新鲜输出集内)。 ### 横向对比