mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-06-28 08:49:38 -04:00
d5c75d944d
# Description of Changes
Graduate **procedures** (and the outgoing HTTP client used from
procedures) out of the `unstable` feature gate /
`SPACETIMEDB_UNSTABLE_FEATURES` across all module libraries. The
`procedure` macro, `ProcedureContext`, `with_tx`/`try_with_tx`,
scheduled procedures, and `ctx.http` are now available without opting
into `unstable`.
**Still gated** (unchanged): HTTP handlers/webhooks, views, RLS /
`client_visibility_filter`, and immediate-scheduling
(`volatile_nonatomic_schedule_immediate`).
Per library:
- **`bindings-sys`**: ungate the `procedure` host-call module + raw ABI
imports (`procedure_start/commit/abort_mut_tx`,
`procedure_http_request`) and `call_no_ret`. Scheduling ABI stays gated.
- **`bindings` (Rust)**: ungate the `procedure` macro,
`ProcedureContext`, `TxContext`/`with_tx`/`try_with_tx`,
`db_read_only`/`get_read_only`, the procedure traits,
`register_procedure`, `__call_procedure__`, and procedure RNG. The
`http` module (previously gated as a unit) now has fine-grained gating
so the outgoing `HttpClient` is exposed while
`HandlerContext`/`Router`/handler macros stay gated.
- **`bindings-csharp`**: drop `[Experimental("STDB_UNSTABLE")]` from
`ProcedureContext.WithTx/TryWithTx` (runtime + codegen) and the
generated `ProcedureTxContext`; FFI snapshots regenerated. Handler
context members + RLS attribute stay gated.
- **`bindings-cpp`**: ungate the procedure ABI (`abi.h`/`FFI.h`),
`tx_execution.h`, the outgoing HTTP client (`http.h`/`http_convert.h`),
and `procedure_context.h`.
`handler_context.h`/`router.h`/`http_handler_macros.h` still `#error`
without `SPACETIMEDB_UNSTABLE_FEATURES`.
- **docs**: remove the procedures beta notices (HTTP handlers stay
marked beta for a later release); regenerate `static/llms.md`.
- **`sdk-test-procedure`**: no longer needs `features = ["unstable"]`.
# API and ABI breaking changes
Not breaking. This is purely additive to the stable surface: procedures
become available **without** the `unstable` feature, while modules that
already opt into `unstable` are unaffected. The wasm host-ABI imports
were already implemented host-side and merely gated module-side, so
there is no ABI version change. (No breaking-change label needed.)
# Expected complexity level and risk
**3/5.** The diff itself is mostly removing
`#[cfg]`/`#ifdef`/`[Experimental]` guards, but the gate bundled
procedures + outgoing HTTP + handlers + the ABI crate together, so the
work was in *separating* procedures from the features that stay gated.
Areas reviewers may want to scrutinize:
- The Rust `http` module went from "gated as a whole" to fine-grained
per-item gating; the outgoing `HttpClient` is exposed while handler
types/macros stay behind `unstable`.
- The `unstable` cut reaches the ABI crate (`bindings-sys`), which is
the only way procedures can compile without the feature.
- C++ separation of the outgoing HTTP client from HTTP handlers across
several headers.
# Testing
- [x] Rust: `cargo check -p spacetimedb` passes in default, `--features
unstable`, and `--no-default-features --features unstable`.
- [x] Rust end-to-end: `sdk-test-procedure` (uses procedures,
`with_tx`/`try_with_tx`, `ctx.http.get`, `new_uuid_v7`, scheduled
procedures) builds **without** `unstable`.
- [x] C#: Codegen + Runtime build; `Codegen.Tests` pass (6/6) after FFI
snapshot regen.
- [x] C++: host syntax-check confirms procedures + `ctx.http.send()`
compile **without** the flag, full headers compile **with** it, and
`handler_context.h` still `#error`s without it. (Not built for the real
wasm/emscripten target locally — relying on CI.)
- [x] `cargo ci lint` components reproduced locally: rustfmt, clippy
(`-D warnings`, default + `unstable`), csharpier, and `cargo doc --deny
warnings`.
- [ ] Reviewer: confirm the wasm bindings + C#/C++ test suites pass in
CI (especially the C++ wasm build, which couldn't run locally).
---------
Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com>
140 lines
4.6 KiB
C#
140 lines
4.6 KiB
C#
namespace SpacetimeDB;
|
|
|
|
#pragma warning disable STDB_UNSTABLE
|
|
public abstract class ProcedureContextBase : Internal.IInternalProcedureContext
|
|
{
|
|
public static Identity Identity => Internal.IProcedureContext.GetIdentity();
|
|
public Identity Sender { get; }
|
|
public ConnectionId? ConnectionId { get; }
|
|
public Random Rng => txState.Rng;
|
|
public Timestamp Timestamp => txState.Timestamp;
|
|
public AuthCtx SenderAuth { get; }
|
|
|
|
// NOTE: The host rejects procedure HTTP requests while a mut transaction is open
|
|
// (WOULD_BLOCK_TRANSACTION). Avoid calling `Http.*` inside WithTx.
|
|
public HttpClient Http { get; } = new();
|
|
|
|
// **Note:** must be 0..=u32::MAX
|
|
protected int CounterUuid = 0;
|
|
private readonly TransactionalContextState<ProcedureTxContextBase> txState;
|
|
|
|
protected ProcedureContextBase(
|
|
Identity sender,
|
|
ConnectionId? connectionId,
|
|
Random random,
|
|
Timestamp time
|
|
)
|
|
{
|
|
Sender = sender;
|
|
ConnectionId = connectionId;
|
|
SenderAuth = AuthCtx.BuildFromSystemTables(connectionId, sender);
|
|
txState = new(
|
|
random,
|
|
time,
|
|
timestamp => new Internal.TxContext(
|
|
CreateLocal(),
|
|
Sender,
|
|
ConnectionId,
|
|
timestamp,
|
|
SenderAuth,
|
|
random
|
|
),
|
|
inner => CreateTxContext(inner)
|
|
);
|
|
}
|
|
|
|
protected abstract ProcedureTxContextBase CreateTxContext(Internal.TxContext inner);
|
|
protected internal abstract LocalBase CreateLocal();
|
|
|
|
public Internal.TxContext EnterTxContext(long timestampMicros) =>
|
|
txState.EnterTxContext(timestampMicros);
|
|
|
|
public void ExitTxContext() => txState.ExitTxContext();
|
|
|
|
public readonly struct TxOutcome<TResult>(bool isSuccess, TResult? value, Exception? error)
|
|
{
|
|
public bool IsSuccess { get; } = isSuccess;
|
|
public TResult? Value { get; } = value;
|
|
public Exception? Error { get; } = error;
|
|
|
|
public static TxOutcome<TResult> Success(TResult value) => new(true, value, null);
|
|
|
|
public static TxOutcome<TResult> Failure(Exception error) => new(false, default, error);
|
|
|
|
public TResult UnwrapOrThrow() =>
|
|
IsSuccess
|
|
? Value!
|
|
: throw (
|
|
Error
|
|
?? new InvalidOperationException("Transaction failed without an error object.")
|
|
);
|
|
|
|
public TResult UnwrapOrThrow(Func<Exception> fallbackFactory) =>
|
|
IsSuccess ? Value! : throw (Error ?? fallbackFactory());
|
|
}
|
|
|
|
public TResult WithTx<TResult>(Func<ProcedureTxContextBase, TResult> body) =>
|
|
txState.WithTx(body);
|
|
|
|
public TxOutcome<TResult> TryWithTx<TResult, TError>(
|
|
Func<ProcedureTxContextBase, Result<TResult, TError>> body
|
|
)
|
|
where TError : Exception
|
|
{
|
|
var outcome = txState.TryWithTx(body);
|
|
return outcome.IsSuccess
|
|
? TxOutcome<TResult>.Success(outcome.Value!)
|
|
: TxOutcome<TResult>.Failure(
|
|
outcome.Error
|
|
?? new InvalidOperationException("Transaction failed without an error object.")
|
|
);
|
|
}
|
|
}
|
|
|
|
public abstract class ProcedureTxContextBase(Internal.TxContext inner) : IRefreshableTxContext
|
|
{
|
|
internal Internal.TxContext Inner { get; private set; } = inner;
|
|
|
|
internal void Refresh(Internal.TxContext inner) => Inner = inner;
|
|
|
|
void IRefreshableTxContext.Refresh(Internal.TxContext inner) => Refresh(inner);
|
|
|
|
public LocalBase Db => (LocalBase)Inner.Db;
|
|
public Identity Sender => Inner.Sender;
|
|
public ConnectionId? ConnectionId => Inner.ConnectionId;
|
|
public Timestamp Timestamp => Inner.Timestamp;
|
|
public AuthCtx SenderAuth => Inner.SenderAuth;
|
|
public Random Rng => Inner.Rng;
|
|
}
|
|
|
|
public abstract class LocalBase : Internal.Local { }
|
|
|
|
internal sealed partial class RuntimeProcedureContext(
|
|
Identity sender,
|
|
ConnectionId? connectionId,
|
|
Random random,
|
|
Timestamp timestamp
|
|
) : ProcedureContextBase(sender, connectionId, random, timestamp)
|
|
{
|
|
private readonly RuntimeLocal _db = new();
|
|
|
|
protected internal override LocalBase CreateLocal() => _db;
|
|
|
|
protected override ProcedureTxContextBase CreateTxContext(Internal.TxContext inner) =>
|
|
_cached ??= new RuntimeProcedureTxContext(inner);
|
|
|
|
private RuntimeProcedureTxContext? _cached;
|
|
}
|
|
|
|
internal sealed class RuntimeProcedureTxContext : ProcedureTxContextBase
|
|
{
|
|
internal RuntimeProcedureTxContext(Internal.TxContext inner)
|
|
: base(inner) { }
|
|
|
|
public new RuntimeLocal Db => (RuntimeLocal)base.Db;
|
|
}
|
|
|
|
internal sealed class RuntimeLocal : LocalBase { }
|
|
|
|
#pragma warning restore STDB_UNSTABLE
|