Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cf032c5
Initial addition of http-handlers for C++ mirrored from Rust
JasonAtClockwork May 1, 2026
088c7ba
Matching to 5fab877
JasonAtClockwork May 11, 2026
272cd57
Fix unstable flag, add smoketests and update docs
JasonAtClockwork May 12, 2026
e212729
Mirror changes in TypeScript for smoketests
JasonAtClockwork May 13, 2026
8600c3b
Lint repair and update from missing autogen
JasonAtClockwork May 14, 2026
2c705f6
Clean up of overuse of (void)
JasonAtClockwork May 15, 2026
3e25d8d
Docs cleanup
JasonAtClockwork May 15, 2026
ca949e5
Do not `msync` the entire offset index file on every transaction (#5018)
joshua-spacetime May 18, 2026
a9fd5e6
Pipeline the websocket send path (#5051)
joshua-spacetime May 18, 2026
451fc78
Fix view auto-migrate with canonical names (#4985)
JasonAtClockwork May 19, 2026
868f65f
Add vue `useProcedure` hook (#4999)
kistz May 19, 2026
8641c17
Client binaries from DigitalOcean -> AWS (#5077)
bfops May 20, 2026
940667d
Add commitlog knobs to server config (#5074)
joshua-spacetime May 20, 2026
f39360b
Batch websocket responses using the v3 protocol (#5061)
joshua-spacetime May 20, 2026
bae0842
Clean up for Ryan's comments
JasonAtClockwork May 20, 2026
c39141d
Undo `.npmrc` in templates (#5084)
bfops May 21, 2026
93a68ad
chore: enable HTTP/2 support for backend server (#5027)
onx2 May 21, 2026
6191736
Merge branch 'phoebe/http-handlers-webhooks' into jlarabie/http-handl…
JasonAtClockwork May 21, 2026
d3b4a96
Drop serde_json arbitrary_precision from workspace (#5001)
clockwork-labs-bot May 21, 2026
7d4a8c1
Fixes to get minimum requirement for namespace changes
JasonAtClockwork May 21, 2026
ae10af5
Merge remote-tracking branch 'origin/master' into jlarabie/http-handl…
clockwork-labs-bot May 21, 2026
8c36f6e
Format C++ codegen
clockwork-labs-bot May 21, 2026
ce3a701
Merge remote-tracking branch 'origin/phoebe/http-handlers-webhooks' i…
JasonAtClockwork May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/attach-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Download artifacts from private base URL
- name: Download artifacts from AWS base URL
env:
RELEASE_TAG: ${{ github.event.inputs.release_tag }}
BASE_URL: ${{ secrets.ARTIFACT_BASE_URL }}
BASE_URL: https://${{ vars.AWS_BUCKET }}.s3.amazonaws.com/refs/tags
run: |
set -euo pipefail
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ on:
push:
branches:
- master
- jgilles/fix-callgrind-again

workflow_dispatch:
inputs:
Expand All @@ -24,8 +23,10 @@ jobs:
benchmark:
name: run criterion benchmarks
runs-on: benchmarks-runner
# disable until we fix the benchmarks
if: false
# filter for a comment containing 'benchmarks please'
if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'benchmarks please')) }}
#if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'benchmarks please')) }}
env:
PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.issue.number || null }}
steps:
Expand Down Expand Up @@ -185,8 +186,10 @@ jobs:
container:
image: rust:1.93.0
options: --privileged
# disable until we fix the benchmarks
if: false
# filter for a comment containing 'benchmarks please'
if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'benchmarks please')) }}
#if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'benchmarks please')) }}
env:
PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.issue.number || null }}
steps:
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,11 @@ jobs:
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
id: extract_branch

- name: Upload to DO Spaces
- name: Upload to AWS S3
uses: shallwefootball/s3-upload-action@master
with:
aws_key_id: ${{ secrets.AWS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}}
aws_bucket: ${{ vars.AWS_BUCKET }}
source_dir: build
endpoint: https://nyc3.digitaloceanspaces.com
destination_dir: ${{ steps.extract_branch.outputs.branch }}
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ arrayvec = "0.7.2"
async-channel = "2.5"
async-stream = "0.3.6"
async-trait = "0.1.68"
axum = { version = "0.7", features = ["tracing"] }
axum = { version = "0.7", features = ["tracing", "http2"] }
axum-extra = { version = "0.9", features = ["typed-header"] }
backtrace = "0.3.66"
base64 = "0.21.2"
Expand Down Expand Up @@ -278,7 +278,7 @@ second-stack = "0.3"
self-replace = "1.5"
semver = "1"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = { version = "1.0.128", features = ["raw_value", "arbitrary_precision"] }
serde_json = { version = "1.0.128", features = ["raw_value"] }
serde_path_to_error = "0.1.9"
serde_with = { version = "3.3.0", features = ["base64", "hex"] }
serial_test = "2.0.0"
Expand Down
46 changes: 3 additions & 43 deletions crates/bindings-cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ target_sources(spacetimedb_cpp_library PRIVATE ${LIBRARY_SOURCES})

# Require C++20 for consumers of this library without forcing global flags
target_compile_features(spacetimedb_cpp_library PUBLIC cxx_std_20)
target_compile_definitions(spacetimedb_cpp_library PRIVATE SPACETIMEDB_UNSTABLE_FEATURES)

# Set include directories
target_include_directories(spacetimedb_cpp_library
Expand Down Expand Up @@ -60,46 +61,5 @@ if(PROJECT_IS_TOP_LEVEL)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()

# ---- Tests ----
# Default: ON only when building this project directly; OFF when used via FetchContent/add_subdirectory
if(CMAKE_VERSION VERSION_LESS 3.21)
# Fallback heuristic for older CMake
set(_is_top_level FALSE)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(_is_top_level TRUE)
endif()
else()
set(_is_top_level ${PROJECT_IS_TOP_LEVEL})
endif()

option(BUILD_TESTS "Build the test suite" ${_is_top_level})

if(BUILD_TESTS AND NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
enable_testing()

# Add test executable
add_executable(test_bsatn tests/main.cpp tests/module_library_unit_tests.cpp)

# Link against the module library
target_link_libraries(test_bsatn PRIVATE spacetimedb_cpp_library)

# Set C++20 standard for tests
target_compile_features(test_bsatn PRIVATE cxx_std_20)

# Add test to CTest
add_test(NAME bsatn_tests COMMAND test_bsatn)

# Add verbose test variant
add_test(NAME bsatn_tests_verbose COMMAND test_bsatn -v)

# Set test properties
set_tests_properties(bsatn_tests PROPERTIES
TIMEOUT 30
LABELS "unit"
)

set_tests_properties(bsatn_tests_verbose PROPERTIES
TIMEOUT 30
LABELS "unit;verbose"
)
endif()
# Unit/compile/smoke test harnesses live under `tests/` as standalone runners
# rather than being built through the top-level library CMake target.
5 changes: 5 additions & 0 deletions crates/bindings-cpp/include/spacetimedb.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@

// Procedure context and macros
#include "spacetimedb/procedure_macros.h"
#ifdef SPACETIMEDB_UNSTABLE_FEATURES
#include "spacetimedb/handler_context.h"
#include "spacetimedb/router.h"
#include "spacetimedb/http_handler_macros.h"
#endif

// =============================================================================
// VIEW SYSTEM
Expand Down
31 changes: 31 additions & 0 deletions crates/bindings-cpp/include/spacetimedb/abi/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,37 @@ int16_t __call_reducer__(
BytesSource args,
BytesSink error);

STDB_EXPORT(__call_view__)
int16_t __call_view__(
uint32_t id,
uint64_t sender_0, uint64_t sender_1, uint64_t sender_2, uint64_t sender_3,
BytesSource args,
BytesSink result);

STDB_EXPORT(__call_view_anon__)
int16_t __call_view_anon__(
uint32_t id,
BytesSource args,
BytesSink result);

STDB_EXPORT(__call_procedure__)
int16_t __call_procedure__(
uint32_t id,
uint64_t sender_0, uint64_t sender_1, uint64_t sender_2, uint64_t sender_3,
uint64_t conn_id_0, uint64_t conn_id_1,
uint64_t timestamp_microseconds,
BytesSource args_source,
BytesSink result_sink);

STDB_EXPORT(__call_http_handler__)
int16_t __call_http_handler__(
uint32_t id,
uint64_t timestamp_microseconds,
BytesSource request_source,
BytesSource request_body_source,
BytesSink response_sink,
BytesSink response_body_sink);

// ========================================================================
// WASI SHIMS
// ========================================================================
Expand Down
92 changes: 92 additions & 0 deletions crates/bindings-cpp/include/spacetimedb/handler_context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#ifndef SPACETIMEDB_HANDLER_CONTEXT_H
#define SPACETIMEDB_HANDLER_CONTEXT_H

#ifndef SPACETIMEDB_UNSTABLE_FEATURES
#error "spacetimedb/handler_context.h requires SPACETIMEDB_UNSTABLE_FEATURES to be enabled"
#endif

#include <spacetimedb/abi/FFI.h>
#include <spacetimedb/bsatn/timestamp.h>
#include <spacetimedb/bsatn/uuid.h>
#include <spacetimedb/http.h>
#include <spacetimedb/internal/tx_execution.h>
#include <spacetimedb/random.h>
#include <spacetimedb/tx_context.h>
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <type_traits>

namespace SpacetimeDB {

struct HandlerContext {
Timestamp timestamp;
HttpClient http;

private:
mutable std::shared_ptr<StdbRng> rng_instance;
mutable uint32_t counter_uuid_ = 0;

public:
HandlerContext() = default;
explicit HandlerContext(Timestamp t) : timestamp(t) {}

Identity identity() const {
std::array<uint8_t, 32> id_bytes;
::identity(id_bytes.data());
return Identity(id_bytes);
}

StdbRng& rng() const {
if (!rng_instance) {
rng_instance = std::make_shared<StdbRng>(timestamp);
}
return *rng_instance;
}

Uuid new_uuid_v4() const {
std::array<uint8_t, 16> random_bytes;
rng().fill_bytes(random_bytes.data(), random_bytes.size());
return Uuid::from_random_bytes_v4(random_bytes);
}

Uuid new_uuid_v7() const {
std::array<uint8_t, 4> random_bytes;
rng().fill_bytes(random_bytes.data(), random_bytes.size());
return Uuid::from_counter_v7(counter_uuid_, timestamp, random_bytes);
}

#ifdef SPACETIMEDB_UNSTABLE_FEATURES
template<typename Func>
auto with_tx(Func&& body) -> decltype(body(std::declval<TxContext&>())) {
auto make_reducer_ctx = [](Timestamp tx_timestamp) {
return ReducerContext(
Identity{},
std::nullopt,
tx_timestamp,
AuthCtx::internal()
);
};
return Internal::with_tx(make_reducer_ctx, body);
}

template<typename Func>
auto try_with_tx(Func&& body) -> decltype(body(std::declval<TxContext&>())) {
auto make_reducer_ctx = [](Timestamp tx_timestamp) {
return ReducerContext(
Identity{},
std::nullopt,
tx_timestamp,
AuthCtx::internal()
);
};
return Internal::try_with_tx(make_reducer_ctx, body);
}
#endif
};

} // namespace SpacetimeDB

#endif // SPACETIMEDB_HANDLER_CONTEXT_H
10 changes: 8 additions & 2 deletions crates/bindings-cpp/include/spacetimedb/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

#pragma once

#ifndef SPACETIMEDB_UNSTABLE_FEATURES
#error "spacetimedb/http.h requires SPACETIMEDB_UNSTABLE_FEATURES to be enabled"
#endif

#include <string>
#include <vector>
#include <optional>
Expand Down Expand Up @@ -312,8 +316,10 @@ class HttpClient {

} // namespace SpacetimeDB

// Include implementation after class definition to avoid circular dependencies
#ifdef SPACETIMEDB_UNSTABLE_FEATURES
// Include implementation dependencies after class definition to avoid circular dependencies
#if defined(SPACETIMEDB_UNSTABLE_FEATURES) && !defined(SPACETIMEDB_HTTP_CONVERT_H)
#include "spacetimedb/logger.h"
#include "spacetimedb/http_convert.h"
#include "spacetimedb/http_client_impl.h"
#endif

Expand Down
15 changes: 5 additions & 10 deletions crates/bindings-cpp/include/spacetimedb/http_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "spacetimedb/http_convert.h"
#include "spacetimedb/abi/abi.h"
#include "spacetimedb/bsatn/bsatn.h"
#include "spacetimedb/internal/Module.h"
#include "spacetimedb/internal/runtime_registration.h"

namespace SpacetimeDB {

Expand All @@ -23,9 +23,9 @@ inline Outcome<HttpResponse> HttpClient::SendImpl(const HttpRequest& request) {
// Prepare body bytes
const std::vector<uint8_t>& body_bytes = request.body.bytes;

// Call host function
// Note: For empty body, we need to pass a valid pointer, not null
const uint8_t* body_ptr = body_bytes.empty() ? reinterpret_cast<const uint8_t*>("") : body_bytes.data();
// The host ABI requires a non-null, in-bounds body pointer even when body_len == 0.
static const uint8_t empty_sentinel = 0;
const uint8_t* body_ptr = body_bytes.empty() ? &empty_sentinel : body_bytes.data();

BytesSource out[2] = {BytesSource{0}, BytesSource{0}};
Status status = procedure_http_request(
Expand All @@ -40,15 +40,11 @@ inline Outcome<HttpResponse> HttpClient::SendImpl(const HttpRequest& request) {
if (status.inner == 21) {
// Read error message from out[0]
std::vector<uint8_t> error_bytes = Internal::ConsumeBytes(out[0]);

LOG_INFO("HTTP: Error bytes: " + std::to_string(error_bytes.size()));


// Decode BSATN string
bsatn::Reader reader(error_bytes.data(), error_bytes.size());
std::string error_message = bsatn::deserialize<std::string>(reader);

LOG_INFO("HTTP: Error message: " + error_message);

return Err<HttpResponse>(std::move(error_message));
}

Expand All @@ -57,7 +53,6 @@ inline Outcome<HttpResponse> HttpClient::SendImpl(const HttpRequest& request) {
return Err<HttpResponse>("HTTP requests are blocked inside transactions. Call HTTP before with_tx() or try_with_tx().");
}

LOG_INFO("HTTP: Unknown error code: " + std::to_string(status.inner));
return Err<HttpResponse>("HTTP request failed with status code: " + std::to_string(status.inner));
}

Expand Down
15 changes: 15 additions & 0 deletions crates/bindings-cpp/include/spacetimedb/http_convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ inline HttpRequest from_wire(const wire::HttpRequest& request) {
return result;
}

inline HttpRequest from_wire(const wire::HttpRequest& request, std::vector<uint8_t> body) {
HttpRequest result = from_wire(request);
result.body.bytes = std::move(body);
return result;
}

// ==================== HttpResponse Conversions ====================

/**
Expand Down Expand Up @@ -268,7 +274,16 @@ inline HttpResponse from_wire(const wire::HttpResponse& response) {
return result;
}

inline std::pair<wire::HttpResponse, std::vector<uint8_t>> to_wire_split(const HttpResponse& response) {
return {to_wire(response), response.body.bytes};
}

} // namespace convert
} // namespace SpacetimeDB

#ifdef SPACETIMEDB_UNSTABLE_FEATURES
#include "spacetimedb/logger.h"
#include "spacetimedb/http_client_impl.h"
#endif

#endif // SPACETIMEDB_HTTP_CONVERT_H
Loading
Loading