diff --git a/crates/bindings-csharp/NATIVEAOT-LLVM.md b/crates/bindings-csharp/NATIVEAOT-LLVM.md
new file mode 100644
index 00000000000..3c1dc2438d3
--- /dev/null
+++ b/crates/bindings-csharp/NATIVEAOT-LLVM.md
@@ -0,0 +1,174 @@
+# Using NativeAOT-LLVM with SpacetimeDB C# Modules
+
+This guide provides instructions for enabling NativeAOT-LLVM compilation for C# SpacetimeDB modules, which can provide performance improvements.
+
+## Overview
+
+NativeAOT-LLVM compiles C# modules to native WebAssembly (WASM) instead of using the Mono runtime.
+
+> [!WARNING]
+> This is currently only supported for Windows server modules and is experimental.
+
+## Prerequisites
+
+- **.NET SDK 8.x** (same version used by SpacetimeDB)
+- **Emscripten SDK (EMSDK)** installed (must contain `upstream/emscripten/emcc.bat`)
+- **(Optional) Binaryen (wasm-opt)** installed and on `PATH` (recommended: `version_116`)
+- **Windows** - NativeAOT-LLVM is currently only supported for Windows server modules
+
+## Prerequisites Installation
+
+### Install Emscripten SDK (EMSDK)
+
+The Emscripten SDK is required for NativeAOT-LLVM compilation:
+
+1. **Download and extract** the Emscripten SDK from `https://github.com/emscripten-core/emsdk`
+ - Example path: `D:\Tools\emsdk`
+
+2. **Set environment variable** (optional - the CLI will detect it automatically):
+ ```
+ $env:EMSDK="D:\Tools\emsdk"
+ ```
+
+### Install Binaryen (Optional)
+
+Binaryen provides `wasm-opt` for WASM optimization (recommended for performance):
+
+1. Download Binaryen https://github.com/WebAssembly/binaryen/releases/tag/version_116 for Windows
+2. Extract to e.g. `D:\Tools\binaryen`
+3. Add `D:\Tools\binaryen\bin` to `PATH`
+
+ To temporarily add to your current PowerShell session:
+ ```
+ $env:PATH += ";D:\Tools\binaryen\bin"
+ ```
+4. Verify:
+ ```
+ wasm-opt --version
+ ```
+
+## Creating a New NativeAOT Project
+
+When creating a new C# project, use the `--native-aot` flag:
+
+```
+spacetime init --lang csharp --native-aot my-native-aot-project
+```
+
+This automatically:
+- Creates a C# project with the required package references
+- Generates a `spacetime.json` with `"native-aot": true`
+- Configures the project for NativeAOT-LLVM compilation
+
+## Converting an Existing Project
+
+1. **Update spacetime.json**
+ Add `"native-aot": true` to your `spacetime.json`:
+ ```json
+ {
+ "module": "your-module-name",
+ "native-aot": true
+ }
+ ```
+
+ **Note:** Once `spacetime.json` has `"native-aot": true`, you can simply run `spacetime publish` without the `--native-aot` flag. The CLI will automatically detect the configuration and use NativeAOT compilation.
+
+2. **Ensure NuGet feed is configured**
+ NativeAOT-LLVM packages come from **dotnet-experimental**. Add to `NuGet.Config`:
+ ```xml
+
+
+
+
+
+
+
+
+ ```
+
+3. **Add NativeAOT package references**
+ Add this `ItemGroup` to your `.csproj`:
+ ```xml
+
+
+
+
+
+ ```
+
+ Your complete `.csproj` should look like:
+ ```xml
+
+
+ net8.0
+ wasi-wasm
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ ```
+
+## Publishing Your NativeAOT Module
+
+After completing either the **Creating a New NativeAOT Project** or **Converting an Existing Project** steps above, you can publish your module normally:
+
+```
+# From your project directory
+spacetime publish your-database-name
+```
+
+If you have `"native-aot": true` in your `spacetime.json`, the CLI will automatically detect this and use NativeAOT compilation. Alternatively, you can use:
+
+```
+spacetime publish --native-aot your-database-name
+```
+
+The CLI will display "Using NativeAOT-LLVM compilation (experimental)" when NativeAOT is enabled.
+
+## Troubleshooting
+
+### Package source mapping enabled
+If you have **package source mapping** enabled in `NuGet.Config`, add mappings for the LLVM packages:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### wasi-experimental workload install fails
+If the CLI cannot install the `wasi-experimental` workload automatically, install it manually:
+
+```
+dotnet workload install wasi-experimental
+```
+
+### Duplicate PackageReference warning
+You may see a `NU1504` warning about duplicate `PackageReference` items. This is expected and non-blocking.
+
+### Code generation failed
+If you see errors like "Code generation failed for method", ensure:
+1. You're using `SpacetimeDB.Runtime` version 2.0.4 or newer
+2. All required package references are in your `.csproj`
+3. The `dotnet-experimental` feed is configured in `NuGet.Config`
+
diff --git a/crates/bindings-csharp/Runtime/Internal/Module.cs b/crates/bindings-csharp/Runtime/Internal/Module.cs
index 8dcf6cebb4e..fcfe683e5a0 100644
--- a/crates/bindings-csharp/Runtime/Internal/Module.cs
+++ b/crates/bindings-csharp/Runtime/Internal/Module.cs
@@ -206,6 +206,36 @@ internal RawModuleDefV10 BuildModuleDefinition()
public static class Module
{
+ // Workaround for NativeAOT-LLVM IL scanner bug:
+ // The scanner fails to compute vtables for TaggedEnum base types when
+ // concrete subtypes are only encountered indirectly (e.g., through Equals
+ // calls on types containing TaggedEnum fields). This occurs when no user
+ // table has indexes/primary keys, so RawIndexAlgorithm is never directly
+ // constructed in user code.
+ // By referencing concrete TaggedEnum subtypes here, we ensure the IL scanner
+ // always processes their vtables. One variant per TaggedEnum is sufficient.
+ [System.Runtime.CompilerServices.MethodImpl(
+ System.Runtime.CompilerServices.MethodImplOptions.NoInlining
+ )]
+ private static void EnsureNativeAotTypeRoots()
+ {
+ // These constructions are never executed at runtime — they exist solely
+ // to make the IL scanner compute vtables for TaggedEnum subtypes.
+ // The condition is always false but the scanner must assume it could be true.
+ if (Environment.TickCount < 0 && Environment.TickCount > 0)
+ {
+ _ = new RawIndexAlgorithm.BTree(null!);
+ _ = new RawConstraintDataV9.Unique(null!);
+ _ = new RawModuleDef.V10(null!);
+ _ = new RawModuleDefV10Section.Typespace(null!);
+ _ = new ExplicitNameEntry.Table(null!);
+ _ = new MiscModuleExport.TypeAlias(null!);
+ _ = new RawMiscModuleExportV9.ColumnDefaultValue(null!);
+ _ = new SpacetimeDB.Filter.Sql(null!);
+ _ = new ViewResultHeader.RowData(default);
+ }
+ }
+
private static readonly RawModuleDefV10 moduleDef = new();
private static readonly List reducers = [];
@@ -419,6 +449,7 @@ private static void Write(this BytesSink sink, byte[] bytes)
public static void __describe_module__(BytesSink description)
{
+ EnsureNativeAotTypeRoots();
try
{
var module = moduleDef.BuildModuleDefinition();
diff --git a/crates/bindings-csharp/Runtime/Runtime.csproj b/crates/bindings-csharp/Runtime/Runtime.csproj
index 2f4350379ee..eb28d3481b6 100644
--- a/crates/bindings-csharp/Runtime/Runtime.csproj
+++ b/crates/bindings-csharp/Runtime/Runtime.csproj
@@ -12,6 +12,7 @@
true
SpacetimeDB
true
+ https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json;$(RestoreAdditionalProjectSources)
@@ -25,6 +26,13 @@
+
+
+
+
+
+
+
diff --git a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets
index b57f5041c4d..5f183e0e040 100644
--- a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets
+++ b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets
@@ -1,32 +1,50 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/crates/cli/src/subcommands/init.rs b/crates/cli/src/subcommands/init.rs
index b4701b41164..31d927720f1 100644
--- a/crates/cli/src/subcommands/init.rs
+++ b/crates/cli/src/subcommands/init.rs
@@ -116,6 +116,7 @@ pub struct TemplateConfig {
pub github_repo: Option,
pub template_def: Option,
pub use_local: bool,
+ pub native_aot: bool,
}
#[derive(Debug, Clone, Default)]
@@ -131,6 +132,8 @@ pub struct InitOptions {
pub non_interactive: bool,
/// When true, suppress the "Next steps" message after init (e.g. when called from `spacetime dev`).
pub skip_next_steps: bool,
+ /// When true, configure C# projects for NativeAOT-LLVM compilation.
+ pub native_aot: bool,
}
impl InitOptions {
@@ -146,6 +149,7 @@ impl InitOptions {
local: args.get_flag("local"),
non_interactive: args.get_flag("non-interactive"),
skip_next_steps: false,
+ native_aot: args.get_flag("native-aot"),
}
}
}
@@ -189,6 +193,12 @@ pub fn cli() -> clap::Command {
.action(clap::ArgAction::SetTrue)
.help("Run in non-interactive mode"),
)
+ .arg(
+ Arg::new("native-aot")
+ .long("native-aot")
+ .action(clap::ArgAction::SetTrue)
+ .help("Configure C# project for NativeAOT-LLVM compilation (experimental, Windows only)"),
+ )
}
pub async fn fetch_templates_list() -> anyhow::Result> {
@@ -346,6 +356,7 @@ fn create_template_config_from_template_str(
github_repo: None,
template_def: Some(template.clone()),
use_local: true,
+ native_aot: false,
})
} else {
// GitHub template
@@ -358,6 +369,7 @@ fn create_template_config_from_template_str(
github_repo: Some(template_str.to_string()),
template_def: None,
use_local: true,
+ native_aot: false,
})
}
}
@@ -525,7 +537,14 @@ pub async fn exec_with_options(config: &mut Config, options: &InitOptions) -> an
)?;
init_from_template(&template_config, &template_config.project_path, is_server_only).await?;
- if let Some(path) = create_default_spacetime_config_if_missing(&project_path)? {
+ // Add NativeAOT-LLVM package references to C# projects if --native-aot was specified
+ if options.native_aot && template_config.server_lang == Some(ServerLanguage::Csharp) {
+ let server_dir = template_config.project_path.join("spacetimedb");
+ add_native_aot_packages_to_csproj(&server_dir)?;
+ }
+
+ let default_server = config.default_server_name().unwrap_or("maincloud");
+ if let Some(path) = create_default_spacetime_config_if_missing(&project_path, options.native_aot, default_server)? {
println!("{} Created {}", "✓".green(), path.display());
}
@@ -605,7 +624,11 @@ fn get_local_database_name(options: &InitOptions, project_name: &str, is_interac
Ok(database_name)
}
-fn create_default_spacetime_config_if_missing(project_path: &Path) -> anyhow::Result tag
+ let new_content = if let Some(pos) = content.rfind("") {
+ let (before, after) = content.split_at(pos);
+ format!("{}{}{}", before.trim_end(), native_aot_item_group, after)
+ } else {
+ anyhow::bail!("Invalid .csproj file: missing tag");
+ };
+
+ std::fs::write(&csproj_path, new_content)?;
+ println!(
+ "{} Added NativeAOT-LLVM package references to {}",
+ "✓".green(),
+ csproj_path.display()
+ );
+
+ // Create NuGet.Config with the dotnet-experimental feed required for NativeAOT-LLVM packages
+ let nuget_config_path = project_path.join("NuGet.Config");
+ let nuget_config_content = r#"
+
+
+
+
+
+
+
+"#;
+
+ std::fs::write(&nuget_config_path, nuget_config_content)?;
+ println!(
+ "{} Created {} with dotnet-experimental feed",
+ "✓".green(),
+ nuget_config_path.display()
+ );
+
+ Ok(())
+}
+
pub fn init_typescript_project(project_path: &Path) -> anyhow::Result<()> {
let export_files = vec![
(
@@ -2015,7 +2117,7 @@ mod tests {
let project_path = temp.path();
std::fs::create_dir_all(project_path.join("spacetimedb")).unwrap();
- let created = create_default_spacetime_config_if_missing(project_path)
+ let created = create_default_spacetime_config_if_missing(project_path, false, "maincloud")
.unwrap()
.expect("expected config to be created");
assert_eq!(created, project_path.join("spacetime.json"));
@@ -2028,6 +2130,23 @@ mod tests {
parsed.get("module-path").and_then(|v| v.as_str()),
Some("./spacetimedb")
);
+ assert!(parsed.get("native-aot").is_none());
+ }
+
+ #[test]
+ fn test_create_default_spacetime_config_with_native_aot() {
+ let temp = tempfile::TempDir::new().unwrap();
+ let project_path = temp.path();
+ std::fs::create_dir_all(project_path.join("spacetimedb")).unwrap();
+
+ let created = create_default_spacetime_config_if_missing(project_path, true, "maincloud")
+ .unwrap()
+ .expect("expected config to be created");
+ assert_eq!(created, project_path.join("spacetime.json"));
+
+ let content = std::fs::read_to_string(&created).unwrap();
+ let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
+ assert_eq!(parsed.get("native-aot").and_then(|v| v.as_bool()), Some(true));
}
#[test]
diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs
index 30faf5e6052..05247b93044 100644
--- a/crates/cli/src/subcommands/publish.rs
+++ b/crates/cli/src/subcommands/publish.rs
@@ -12,8 +12,8 @@ use std::{env, fs};
use crate::common_args::ClearMode;
use crate::config::Config;
use crate::spacetime_config::{
- find_and_load_with_env, CommandConfig, CommandSchema, CommandSchemaBuilder, FlatTarget, Key, LoadedConfig,
- SpacetimeConfig,
+ find_and_load_with_env, find_and_load_with_env_from, CommandConfig, CommandSchema, CommandSchemaBuilder,
+ FlatTarget, Key, LoadedConfig, SpacetimeConfig,
};
use crate::util::{add_auth_header_opt, get_auth_header, strip_verbatim_prefix, AuthHeader, ResponseExt};
use crate::util::{decode_identity, y_or_n};
@@ -33,6 +33,7 @@ pub fn build_publish_schema(command: &clap::Command) -> Result(
.collect();
if matched.is_empty() {
- anyhow::bail!(
- "No database target matches '{}'. Available databases: {}",
- cli_database,
- spacetime_config
- .collect_all_targets_with_inheritance()
- .iter()
- .filter_map(|t| t.fields.get("database").and_then(|v| v.as_str()))
- .collect::>()
- .join(", ")
- );
+ // When there is exactly one target in the config and the CLI-provided
+ // database name doesn't match it, use that target's settings (e.g.
+ // native-aot, module-path, build-options) and let CommandConfig merge
+ // the CLI database name on top. This handles the common case where
+ // `spacetime init` generated a random database suffix that differs
+ // from the name the user passes on the CLI, while still picking up
+ // module-specific config.
+ let all_targets = spacetime_config.collect_all_targets_with_inheritance();
+ if all_targets.len() == 1 {
+ all_targets
+ } else {
+ anyhow::bail!(
+ "No database target matches '{}'. Available databases: {}",
+ cli_database,
+ all_targets
+ .iter()
+ .filter_map(|t| t.fields.get("database").and_then(|v| v.as_str()))
+ .collect::>()
+ .join(", ")
+ );
+ }
+ } else {
+ matched
}
-
- matched
} else {
all_targets
};
@@ -223,6 +235,12 @@ i.e. only lowercase ASCII letters and numbers, separated by dashes."),
.action(Set)
.help("Environment name for config file layering (e.g., dev, staging)")
)
+ .arg(
+ Arg::new("native_aot")
+ .long("native-aot")
+ .action(SetTrue)
+ .help("Use NativeAOT-LLVM compilation for C# modules (experimental, Windows only)")
+ )
.after_help("Run `spacetime help publish` for more detailed information.")
}
@@ -293,13 +311,23 @@ pub async fn exec_with_options(
let env = args.get_one::("env").map(|s| s.as_str());
// Get publish configs (from spacetime.json or empty)
- let owned_loaded;
+ let mut owned_loaded;
let loaded_config_ref = if no_config {
None
} else if let Some(pre) = pre_loaded_config {
Some(pre)
} else {
+ // First, try to load config from current directory
owned_loaded = find_and_load_with_env(env)?;
+
+ // If no config found and --module-path is specified, try loading from module path.
+ if owned_loaded.is_none()
+ && args.contains_id("module_path")
+ && let Some(module_path) = args.get_one::("module_path")
+ {
+ owned_loaded = find_and_load_with_env_from(env, module_path.clone())?;
+ }
+
owned_loaded.as_ref().inspect(|loaded| {
if !quiet_config {
for path in &loaded.loaded_files {
@@ -420,6 +448,7 @@ async fn execute_publish_configs<'a>(
let parent = parent_opt.as_deref();
let org_opt = command_config.get_one::("organization")?;
let org = org_opt.as_deref();
+ let native_aot = command_config.get_one::("native_aot")?.unwrap_or(false);
// If the user didn't specify an identity and we didn't specify an anonymous identity, then
// we want to use the default identity
@@ -447,6 +476,16 @@ async fn execute_publish_configs<'a>(
println!("(JS) Skipping build. Instead we are publishing {}", path.display());
(path.clone(), "Js")
} else {
+ // Set EXPERIMENTAL_WASM_AOT environment variable if native_aot is enabled
+ // This is read by the C# build system (MSBuild) and by csharp.rs to determine output paths
+ if native_aot {
+ println!("Using NativeAOT-LLVM compilation (experimental)");
+ // SAFETY: We are single-threaded at this point and no other code is reading
+ // this environment variable concurrently.
+ unsafe {
+ env::set_var("EXPERIMENTAL_WASM_AOT", "1");
+ }
+ }
build::exec_with_argstring(
path_to_project
.as_ref()
diff --git a/crates/smoketests/tests/smoketests/cli/publish.rs b/crates/smoketests/tests/smoketests/cli/publish.rs
index b6e29e1fb57..50b32ce3e80 100644
--- a/crates/smoketests/tests/smoketests/cli/publish.rs
+++ b/crates/smoketests/tests/smoketests/cli/publish.rs
@@ -223,9 +223,7 @@ fn cli_publish_with_config_but_no_match_uses_cli_args() {
// Create a config with a different database name
let config_content = r#"{
- "publish": {
- "database": "config-db-name"
- }
+ "database": "config-db-name"
}"#;
std::fs::write(module_dir.join("spacetime.json"), config_content).expect("failed to write config");
diff --git a/crates/smoketests/tests/smoketests/csharp_aot_module.rs b/crates/smoketests/tests/smoketests/csharp_aot_module.rs
new file mode 100644
index 00000000000..56df1100861
--- /dev/null
+++ b/crates/smoketests/tests/smoketests/csharp_aot_module.rs
@@ -0,0 +1,63 @@
+#![allow(clippy::disallowed_macros)]
+use spacetimedb_guard::ensure_binaries_built;
+use spacetimedb_smoketests::{have_emscripten, require_dotnet, workspace_root};
+use std::process::Command;
+
+/// Test NativeAOT-LLVM build path for C# modules.
+/// Requires emscripten to be installed.
+/// Only runs on Windows since runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM
+/// is not available on the dotnet-experimental NuGet feed.
+#[test]
+fn test_build_csharp_module_aot() {
+ require_dotnet!();
+
+ // NativeAOT-LLVM is only available on Windows
+ if std::env::consts::OS != "windows" {
+ eprintln!("Skipping AOT test - NativeAOT-LLVM for .NET 8 only available on Windows");
+ return;
+ }
+
+ // Check for emscripten - fail with helpful message if not available
+ // Uses have_emscripten() which checks for both `emcc` and `emcc.bat` on Windows
+ if !have_emscripten() {
+ panic!(
+ "NativeAOT-LLVM test requires emscripten but it was not found.\n\
+ Install from: https://emscripten.org/docs/getting_started/downloads.html\n\
+ Or ensure `emcc` is in your PATH."
+ );
+ }
+
+ let workspace = workspace_root();
+ let _cli_path = ensure_binaries_built();
+
+ // Create isolated NuGet packages folder to avoid file lock conflicts
+ // NativeAOT-LLVM packages contain DLLs that stay locked and interfere with other tests
+ let nuget_packages_dir = tempfile::tempdir().expect("Failed to create temp directory for NuGet packages");
+
+ // Set EXPERIMENTAL_WASM_AOT=1 for this specific build
+ // Build sdk-test-cs with NativeAOT-LLVM
+ let mut cmd = Command::new("dotnet");
+ cmd.arg("publish")
+ .arg("-c")
+ .arg("Release")
+ .current_dir(workspace.join("modules/sdk-test-cs"))
+ .env("EXPERIMENTAL_WASM_AOT", "1")
+ .env("NUGET_PACKAGES", nuget_packages_dir.path());
+
+ let output = cmd.output().expect("Failed to run dotnet publish");
+
+ assert!(
+ output.status.success(),
+ "NativeAOT-LLVM publish failed:\nstdout: {}\nstderr: {}",
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr)
+ );
+
+ // Clean up temp dir explicitly to verify no file locks remain
+ // This ensures subsequent tests can clear NuGet locals without conflicts
+ drop(nuget_packages_dir);
+
+ // Verify StdbModule.wasm was produced
+ let wasm_path = workspace.join("modules/sdk-test-cs/bin/Release/net8.0/wasi-wasm/publish/StdbModule.wasm");
+ assert!(wasm_path.exists(), "StdbModule.wasm not found at {:?}", wasm_path);
+}
diff --git a/crates/smoketests/tests/smoketests/mod.rs b/crates/smoketests/tests/smoketests/mod.rs
index b8342e9f41a..ce26d29d09f 100644
--- a/crates/smoketests/tests/smoketests/mod.rs
+++ b/crates/smoketests/tests/smoketests/mod.rs
@@ -9,6 +9,7 @@ mod client_connection_errors;
mod confirmed_reads;
mod connect_disconnect_from_cli;
mod create_project;
+mod csharp_aot_module;
mod csharp_module;
mod default_module_clippy;
mod delete_database;
diff --git a/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md b/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md
index 8c43c9573b2..fe76039a240 100644
--- a/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md
+++ b/docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md
@@ -111,6 +111,7 @@ Run `spacetime help publish` for more detailed information.
* `-y`, `--yes` — Run non-interactively wherever possible. This will answer "yes" to almost all prompts, but will sometimes answer "no" to preserve non-interactivity (e.g. when prompting whether to log in with spacetimedb.com).
* `--no-config` — Ignore spacetime.json configuration
* `--env ` — Environment name for config file layering (e.g., dev, staging)
+* `--native-aot` — Use NativeAOT-LLVM compilation for C# modules (experimental, Windows only)
@@ -414,6 +415,7 @@ Initializes a new spacetime project.
* `-t`, `--template ` — Template ID or GitHub repository (owner/repo or URL)
* `--local` — Use local deployment instead of Maincloud
* `--non-interactive` — Run in non-interactive mode
+* `--native-aot` — Configure C# project for NativeAOT-LLVM compilation (experimental, Windows only)
diff --git a/modules/sdk-test-cs/sdk-test-cs.csproj b/modules/sdk-test-cs/sdk-test-cs.csproj
index d4caede5ffa..09cf1192bb7 100644
--- a/modules/sdk-test-cs/sdk-test-cs.csproj
+++ b/modules/sdk-test-cs/sdk-test-cs.csproj
@@ -9,5 +9,9 @@
-
+
+
+
+
+
diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs
index dc028b86617..4e254fed3c7 100644
--- a/sdks/csharp/examples~/regression-tests/client/Program.cs
+++ b/sdks/csharp/examples~/regression-tests/client/Program.cs
@@ -82,6 +82,12 @@ void OnConnected(DbConnection conn, Identity identity, string authToken)
.AddQuery(qb => qb.From.IenumerablePlayersFromIter())
.AddQuery(qb => qb.From.IenumerableAdminsFromFilter())
.AddQuery(qb => qb.From.IenumerablePlayersWithLevels())
+ .AddQuery(qb => qb.From.EqualityPerson())
+ .AddQuery(qb => qb.From.EqualityProduct())
+ .AddQuery(qb => qb.From.EqualityOrder())
+ .AddQuery(qb => qb.From.PlayerAction())
+ .AddQuery(qb => qb.From.ActionBatch())
+ .AddQuery(qb => qb.From.LogEntry())
.Subscribe();
// If testing against Rust, the indexed parameter will need to be changed to: ulong indexed
@@ -296,6 +302,96 @@ string name
waiting--;
ValidateCommittedReducer("InsertViewPkMembershipSecondary", ctx);
};
+
+ // Equality test reducer callbacks
+ conn.Reducers.OnRunAllEqualityTests += (ReducerEventContext ctx) =>
+ {
+ Log.Info("Got RunAllEqualityTests callback");
+ // Note: waiting-- happens after validation in this callback
+ Debug.Assert(
+ ctx.Event.Status is Status.Committed,
+ $"RunAllEqualityTests should commit, got {ctx.Event.Status}"
+ );
+
+ // Validate results while subscription is still active
+ // Must happen here before UnsubscribeThen removes the data
+ if (ctx.Event.Status is Status.Committed)
+ {
+ Log.Info("Validating equality test results...");
+ ValidateEqualityResults(ctx);
+ Log.Info("Equality tests completed and validated");
+ }
+ // Decrement waiting here after validation is complete
+ waiting--;
+ };
+
+ conn.Reducers.OnAddEqualityPerson += (ReducerEventContext ctx, uint id, string name) =>
+ {
+ Log.Info($"Got AddEqualityPerson callback: id={id}, name={name}");
+ // Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
+ };
+
+ conn.Reducers.OnAddEqualityProduct += (ReducerEventContext ctx, uint id, string name, int price, int quantity) =>
+ {
+ Log.Info($"Got AddEqualityProduct callback: id={id}, name={name}");
+ // Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
+ };
+
+ conn.Reducers.OnRunEqualityTests += (ReducerEventContext ctx) =>
+ {
+ Log.Info("Got RunEqualityTests callback");
+ // Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
+ };
+
+ conn.Reducers.OnRunComplexEqualityTests += (ReducerEventContext ctx) =>
+ {
+ Log.Info("Got RunComplexEqualityTests callback");
+ // Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
+ };
+
+ conn.Reducers.OnRunEnumEqualityTests += (ReducerEventContext ctx) =>
+ {
+ Log.Info("Got RunEnumEqualityTests callback");
+ // Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
+ };
+
+ // Equality test table insert callbacks
+ conn.Db.EqualityPerson.OnInsert += (EventContext ctx, EqualityPerson row) =>
+ {
+ Log.Info($"EqualityPerson.OnInsert: Id={row.Id}, Name={row.Name}");
+ };
+
+ conn.Db.EqualityProduct.OnInsert += (EventContext ctx, EqualityProduct row) =>
+ {
+ Log.Info($"EqualityProduct.OnInsert: Id={row.Id}, Name={row.Name}, Price={row.Price}");
+ };
+
+ conn.Db.EqualityOrder.OnInsert += (EventContext ctx, EqualityOrder row) =>
+ {
+ Log.Info($"EqualityOrder.OnInsert: Id={row.Id}, Customer={row.CustomerName}");
+ };
+
+ conn.Db.PlayerAction.OnInsert += (EventContext ctx, PlayerAction row) =>
+ {
+ var actionDesc = row.Action switch
+ {
+ GameAction.Move(var m) => $"Move({m})",
+ GameAction.Attack(var a) => $"Attack({a})",
+ GameAction.Defend(var d) => $"Defend({d})",
+ _ => "Unknown"
+ };
+ Log.Info($"PlayerAction.OnInsert: Id={row.Id}, Action={actionDesc}");
+ };
+
+ conn.Db.ActionBatch.OnInsert += (EventContext ctx, ActionBatch row) =>
+ {
+ Log.Info($"ActionBatch.OnInsert: Id={row.Id}, Actions.Count={row.Actions?.Count ?? 0}");
+ };
+
+ conn.Db.LogEntry.OnInsert += (EventContext ctx, LogEntry row) =>
+ {
+ Log.Info($"LogEntry.OnInsert: {row.Message} at {row.Timestamp}");
+ };
}
const uint MAX_ID = 10;
@@ -570,6 +666,78 @@ void ValidateIEnumerableViews(IRemoteDbContext conn)
Log.Debug("IEnumerable views validation completed successfully");
}
+void ValidateEqualitySubscriptions(IRemoteDbContext conn)
+{
+ Log.Debug("Checking equality test subscriptions...");
+
+ // Verify all equality test tables are accessible
+ Debug.Assert(conn.Db.EqualityPerson != null, "EqualityPerson subscription should not be null");
+ Debug.Assert(conn.Db.EqualityProduct != null, "EqualityProduct subscription should not be null");
+ Debug.Assert(conn.Db.EqualityOrder != null, "EqualityOrder subscription should not be null");
+ Debug.Assert(conn.Db.PlayerAction != null, "PlayerAction subscription should not be null");
+ Debug.Assert(conn.Db.ActionBatch != null, "ActionBatch subscription should not be null");
+ Debug.Assert(conn.Db.LogEntry != null, "LogEntry subscription should not be null");
+
+ Log.Debug("Equality test subscriptions validated successfully");
+}
+
+void RunEqualityTests(SubscriptionEventContext ctx)
+{
+ Log.Debug("Running equality tests...");
+
+ // Single waiting increment for the entire equality test phase
+ // This ensures we wait for all callbacks AND validation before proceeding
+ waiting++;
+
+ // Test 1: Add a person
+ Log.Debug("Equality Test: Adding EqualityPerson...");
+ ctx.Reducers.AddEqualityPerson(1, "Alice");
+
+ // Test 2: Add a product
+ Log.Debug("Equality Test: Adding EqualityProduct...");
+ ctx.Reducers.AddEqualityProduct(1, "Widget", 999, 100);
+
+ // Test 3: Run the comprehensive equality test suite
+ Log.Debug("Equality Test: Running RunAllEqualityTests...");
+ ctx.Reducers.RunAllEqualityTests();
+
+ Log.Debug("Equality tests initiated - waiting for completion");
+}
+
+void ValidateEqualityResults(IRemoteDbContext conn)
+{
+ Log.Debug("Validating equality test results...");
+
+ // Validate EqualityPerson data
+ var alice = conn.Db.EqualityPerson.Id.Find(1);
+ Debug.Assert(alice != null, "Alice should be in EqualityPerson table");
+ Debug.Assert(alice.Name == "Alice", $"Expected Alice, got {alice.Name}");
+
+ // Validate EqualityProduct data
+ var product = conn.Db.EqualityProduct.Id.Find(1);
+ Debug.Assert(product != null, "Widget should be in EqualityProduct table");
+ Debug.Assert(product.Name == "Widget", $"Expected Widget, got {product.Name}");
+ Debug.Assert(product.Price == 999, $"Expected Price=999, got {product.Price}");
+
+ // Validate PlayerAction data (inserted by TestSumTypeEquality)
+ var actions = conn.Db.PlayerAction.Iter().ToList();
+ Debug.Assert(actions.Count >= 2, $"Expected at least 2 PlayerActions, got {actions.Count}");
+
+ // Validate ActionBatch data (inserted by TestListOfNullableSumTypes)
+ var batches = conn.Db.ActionBatch.Iter().ToList();
+ Debug.Assert(batches.Count >= 1, $"Expected at least 1 ActionBatch, got {batches.Count}");
+ var batch = batches.FirstOrDefault(b => b.Id == 1);
+ Debug.Assert(batch != null, "Expected ActionBatch with Id=1");
+ Debug.Assert(batch.Actions != null, "ActionBatch should have non-null Actions list");
+ Debug.Assert(batch.Actions.Count == 4, $"Expected 4 actions (2 null), got {batch.Actions.Count}");
+
+ // Validate LogEntry data (inserted by TestTableWithoutPrimaryKey)
+ var logEntries = conn.Db.LogEntry.Iter().ToList();
+ Debug.Assert(logEntries.Count >= 2, $"Expected at least 2 LogEntries, got {logEntries.Count}");
+
+ Log.Debug("Equality test results validated successfully");
+}
+
void ValidateSemijoinSubscriptions(IRemoteDbContext conn, Identity identity)
{
Log.Debug("Checking typed semijoin subscriptions...");
@@ -882,6 +1050,10 @@ void OnSubscriptionApplied(SubscriptionEventContext context)
ValidateIEnumerableViews(context);
ValidateSemijoinSubscriptions(context, context.Identity!.Value);
+ // Run equality tests
+ ValidateEqualitySubscriptions(context);
+ RunEqualityTests(context);
+
// Do some operations that alter row state;
// we will check that everything is in sync in the callbacks for these reducer calls.
Log.Debug("Calling Add");
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityOrder.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityOrder.g.cs
new file mode 100644
index 00000000000..68fcf57a82c
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityOrder.g.cs
@@ -0,0 +1,78 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void AddEqualityOrderHandler(ReducerEventContext ctx, uint id, string? customerName, System.Collections.Generic.List? items);
+ public event AddEqualityOrderHandler? OnAddEqualityOrder;
+
+ public void AddEqualityOrder(uint id, string? customerName, System.Collections.Generic.List? items)
+ {
+ conn.InternalCallReducer(new Reducer.AddEqualityOrder(id, customerName, items));
+ }
+
+ public bool InvokeAddEqualityOrder(ReducerEventContext ctx, Reducer.AddEqualityOrder args)
+ {
+ if (OnAddEqualityOrder == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnAddEqualityOrder(
+ ctx,
+ args.Id,
+ args.CustomerName,
+ args.Items
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class AddEqualityOrder : Reducer, IReducerArgs
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "customer_name")]
+ public string? CustomerName;
+ [DataMember(Name = "items")]
+ public System.Collections.Generic.List? Items;
+
+ public AddEqualityOrder(
+ uint Id,
+ string? CustomerName,
+ System.Collections.Generic.List? Items
+ )
+ {
+ this.Id = Id;
+ this.CustomerName = CustomerName;
+ this.Items = Items;
+ }
+
+ public AddEqualityOrder()
+ {
+ }
+
+ string IReducerArgs.ReducerName => "add_equality_order";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityPerson.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityPerson.g.cs
new file mode 100644
index 00000000000..fd77b6f508b
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityPerson.g.cs
@@ -0,0 +1,74 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void AddEqualityPersonHandler(ReducerEventContext ctx, uint id, string name);
+ public event AddEqualityPersonHandler? OnAddEqualityPerson;
+
+ public void AddEqualityPerson(uint id, string name)
+ {
+ conn.InternalCallReducer(new Reducer.AddEqualityPerson(id, name));
+ }
+
+ public bool InvokeAddEqualityPerson(ReducerEventContext ctx, Reducer.AddEqualityPerson args)
+ {
+ if (OnAddEqualityPerson == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnAddEqualityPerson(
+ ctx,
+ args.Id,
+ args.Name
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class AddEqualityPerson : Reducer, IReducerArgs
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "name")]
+ public string Name;
+
+ public AddEqualityPerson(
+ uint Id,
+ string Name
+ )
+ {
+ this.Id = Id;
+ this.Name = Name;
+ }
+
+ public AddEqualityPerson()
+ {
+ this.Name = "";
+ }
+
+ string IReducerArgs.ReducerName => "add_equality_person";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityProduct.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityProduct.g.cs
new file mode 100644
index 00000000000..5e7830c3efd
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/AddEqualityProduct.g.cs
@@ -0,0 +1,84 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void AddEqualityProductHandler(ReducerEventContext ctx, uint id, string name, int price, int quantity);
+ public event AddEqualityProductHandler? OnAddEqualityProduct;
+
+ public void AddEqualityProduct(uint id, string name, int price, int quantity)
+ {
+ conn.InternalCallReducer(new Reducer.AddEqualityProduct(id, name, price, quantity));
+ }
+
+ public bool InvokeAddEqualityProduct(ReducerEventContext ctx, Reducer.AddEqualityProduct args)
+ {
+ if (OnAddEqualityProduct == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnAddEqualityProduct(
+ ctx,
+ args.Id,
+ args.Name,
+ args.Price,
+ args.Quantity
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class AddEqualityProduct : Reducer, IReducerArgs
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "name")]
+ public string Name;
+ [DataMember(Name = "price")]
+ public int Price;
+ [DataMember(Name = "quantity")]
+ public int Quantity;
+
+ public AddEqualityProduct(
+ uint Id,
+ string Name,
+ int Price,
+ int Quantity
+ )
+ {
+ this.Id = Id;
+ this.Name = Name;
+ this.Price = Price;
+ this.Quantity = Quantity;
+ }
+
+ public AddEqualityProduct()
+ {
+ this.Name = "";
+ }
+
+ string IReducerArgs.ReducerName => "add_equality_product";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunAllEqualityTests.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunAllEqualityTests.g.cs
new file mode 100644
index 00000000000..bb1af3abe8d
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunAllEqualityTests.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void RunAllEqualityTestsHandler(ReducerEventContext ctx);
+ public event RunAllEqualityTestsHandler? OnRunAllEqualityTests;
+
+ public void RunAllEqualityTests()
+ {
+ conn.InternalCallReducer(new Reducer.RunAllEqualityTests());
+ }
+
+ public bool InvokeRunAllEqualityTests(ReducerEventContext ctx, Reducer.RunAllEqualityTests args)
+ {
+ if (OnRunAllEqualityTests == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnRunAllEqualityTests(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class RunAllEqualityTests : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "run_all_equality_tests";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunComplexEqualityTests.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunComplexEqualityTests.g.cs
new file mode 100644
index 00000000000..db0cec16291
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunComplexEqualityTests.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void RunComplexEqualityTestsHandler(ReducerEventContext ctx);
+ public event RunComplexEqualityTestsHandler? OnRunComplexEqualityTests;
+
+ public void RunComplexEqualityTests()
+ {
+ conn.InternalCallReducer(new Reducer.RunComplexEqualityTests());
+ }
+
+ public bool InvokeRunComplexEqualityTests(ReducerEventContext ctx, Reducer.RunComplexEqualityTests args)
+ {
+ if (OnRunComplexEqualityTests == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnRunComplexEqualityTests(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class RunComplexEqualityTests : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "run_complex_equality_tests";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunEnumEqualityTests.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunEnumEqualityTests.g.cs
new file mode 100644
index 00000000000..95b9cc87dd8
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunEnumEqualityTests.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void RunEnumEqualityTestsHandler(ReducerEventContext ctx);
+ public event RunEnumEqualityTestsHandler? OnRunEnumEqualityTests;
+
+ public void RunEnumEqualityTests()
+ {
+ conn.InternalCallReducer(new Reducer.RunEnumEqualityTests());
+ }
+
+ public bool InvokeRunEnumEqualityTests(ReducerEventContext ctx, Reducer.RunEnumEqualityTests args)
+ {
+ if (OnRunEnumEqualityTests == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnRunEnumEqualityTests(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class RunEnumEqualityTests : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "run_enum_equality_tests";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunEqualityTests.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunEqualityTests.g.cs
new file mode 100644
index 00000000000..253a19614b1
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/RunEqualityTests.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void RunEqualityTestsHandler(ReducerEventContext ctx);
+ public event RunEqualityTestsHandler? OnRunEqualityTests;
+
+ public void RunEqualityTests()
+ {
+ conn.InternalCallReducer(new Reducer.RunEqualityTests());
+ }
+
+ public bool InvokeRunEqualityTests(ReducerEventContext ctx, Reducer.RunEqualityTests args)
+ {
+ if (OnRunEqualityTests == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnRunEqualityTests(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class RunEqualityTests : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "run_equality_tests";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ScheduleTask.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ScheduleTask.g.cs
new file mode 100644
index 00000000000..fcf7c89b2d5
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ScheduleTask.g.cs
@@ -0,0 +1,66 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void ScheduleTaskHandler(ReducerEventContext ctx, ulong delayMicros);
+ public event ScheduleTaskHandler? OnScheduleTask;
+
+ public void ScheduleTask(ulong delayMicros)
+ {
+ conn.InternalCallReducer(new Reducer.ScheduleTask(delayMicros));
+ }
+
+ public bool InvokeScheduleTask(ReducerEventContext ctx, Reducer.ScheduleTask args)
+ {
+ if (OnScheduleTask == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnScheduleTask(
+ ctx,
+ args.DelayMicros
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class ScheduleTask : Reducer, IReducerArgs
+ {
+ [DataMember(Name = "delay_micros")]
+ public ulong DelayMicros;
+
+ public ScheduleTask(ulong DelayMicros)
+ {
+ this.DelayMicros = DelayMicros;
+ }
+
+ public ScheduleTask()
+ {
+ }
+
+ string IReducerArgs.ReducerName => "schedule_task";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestListOfNullableSumTypes.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestListOfNullableSumTypes.g.cs
new file mode 100644
index 00000000000..023c80f2685
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestListOfNullableSumTypes.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void TestListOfNullableSumTypesHandler(ReducerEventContext ctx);
+ public event TestListOfNullableSumTypesHandler? OnTestListOfNullableSumTypes;
+
+ public void TestListOfNullableSumTypes()
+ {
+ conn.InternalCallReducer(new Reducer.TestListOfNullableSumTypes());
+ }
+
+ public bool InvokeTestListOfNullableSumTypes(ReducerEventContext ctx, Reducer.TestListOfNullableSumTypes args)
+ {
+ if (OnTestListOfNullableSumTypes == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnTestListOfNullableSumTypes(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class TestListOfNullableSumTypes : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "test_list_of_nullable_sum_types";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestReducerWithSumTypeParam.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestReducerWithSumTypeParam.g.cs
new file mode 100644
index 00000000000..1c9949b917c
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestReducerWithSumTypeParam.g.cs
@@ -0,0 +1,67 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void TestReducerWithSumTypeParamHandler(ReducerEventContext ctx, SpacetimeDB.Types.GameAction action);
+ public event TestReducerWithSumTypeParamHandler? OnTestReducerWithSumTypeParam;
+
+ public void TestReducerWithSumTypeParam(SpacetimeDB.Types.GameAction action)
+ {
+ conn.InternalCallReducer(new Reducer.TestReducerWithSumTypeParam(action));
+ }
+
+ public bool InvokeTestReducerWithSumTypeParam(ReducerEventContext ctx, Reducer.TestReducerWithSumTypeParam args)
+ {
+ if (OnTestReducerWithSumTypeParam == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnTestReducerWithSumTypeParam(
+ ctx,
+ args.Action
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class TestReducerWithSumTypeParam : Reducer, IReducerArgs
+ {
+ [DataMember(Name = "action")]
+ public GameAction Action;
+
+ public TestReducerWithSumTypeParam(GameAction Action)
+ {
+ this.Action = Action;
+ }
+
+ public TestReducerWithSumTypeParam()
+ {
+ this.Action = null!;
+ }
+
+ string IReducerArgs.ReducerName => "test_reducer_with_sum_type_param";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestSumTypeEquality.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestSumTypeEquality.g.cs
new file mode 100644
index 00000000000..850026db3f4
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestSumTypeEquality.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void TestSumTypeEqualityHandler(ReducerEventContext ctx);
+ public event TestSumTypeEqualityHandler? OnTestSumTypeEquality;
+
+ public void TestSumTypeEquality()
+ {
+ conn.InternalCallReducer(new Reducer.TestSumTypeEquality());
+ }
+
+ public bool InvokeTestSumTypeEquality(ReducerEventContext ctx, Reducer.TestSumTypeEquality args)
+ {
+ if (OnTestSumTypeEquality == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnTestSumTypeEquality(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class TestSumTypeEquality : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "test_sum_type_equality";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestTableWithoutPrimaryKey.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestTableWithoutPrimaryKey.g.cs
new file mode 100644
index 00000000000..8f4ff7e9fc5
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/TestTableWithoutPrimaryKey.g.cs
@@ -0,0 +1,53 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteReducers : RemoteBase
+ {
+ public delegate void TestTableWithoutPrimaryKeyHandler(ReducerEventContext ctx);
+ public event TestTableWithoutPrimaryKeyHandler? OnTestTableWithoutPrimaryKey;
+
+ public void TestTableWithoutPrimaryKey()
+ {
+ conn.InternalCallReducer(new Reducer.TestTableWithoutPrimaryKey());
+ }
+
+ public bool InvokeTestTableWithoutPrimaryKey(ReducerEventContext ctx, Reducer.TestTableWithoutPrimaryKey args)
+ {
+ if (OnTestTableWithoutPrimaryKey == null)
+ {
+ if (InternalOnUnhandledReducerError != null)
+ {
+ switch (ctx.Event.Status)
+ {
+ case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
+ case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
+ }
+ }
+ return false;
+ }
+ OnTestTableWithoutPrimaryKey(
+ ctx
+ );
+ return true;
+ }
+ }
+
+ public abstract partial class Reducer
+ {
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class TestTableWithoutPrimaryKey : Reducer, IReducerArgs
+ {
+ string IReducerArgs.ReducerName => "test_table_without_primary_key";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs
index 474129690e1..17395d94485 100644
--- a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs
@@ -1,7 +1,7 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
-// This was generated using spacetimedb cli version 2.0.5 (commit c095e61ada08c525487dce5279fdd0be472a2131).
+// This was generated using spacetimedb cli version 2.1.0 (commit 266eff98ab9e92e3c9308f101460151eea778d8e).
#nullable enable
@@ -29,12 +29,17 @@ public RemoteTables(DbConnection conn)
{
AddTable(Admins = new(conn));
AddTable(Account = new(conn));
+ AddTable(ActionBatch = new(conn));
AddTable(AllViewPkPlayers = new(conn));
+ AddTable(EqualityOrder = new(conn));
+ AddTable(EqualityPerson = new(conn));
+ AddTable(EqualityProduct = new(conn));
AddTable(ExampleData = new(conn));
AddTable(FindWhereTest = new(conn));
AddTable(IenumerableAdminsFromFilter = new(conn));
AddTable(IenumerablePlayersFromIter = new(conn));
AddTable(IenumerablePlayersWithLevels = new(conn));
+ AddTable(LogEntry = new(conn));
AddTable(MyAccount = new(conn));
AddTable(MyAccountMissing = new(conn));
AddTable(MyLog = new(conn));
@@ -45,6 +50,7 @@ public RemoteTables(DbConnection conn)
AddTable(NullableVec = new(conn));
AddTable(NullableVecView = new(conn));
AddTable(Player = new(conn));
+ AddTable(PlayerAction = new(conn));
AddTable(PlayerLevel = new(conn));
AddTable(PlayersAtLevelOne = new(conn));
AddTable(RetryLog = new(conn));
@@ -564,12 +570,17 @@ public sealed class QueryBuilder
{
new QueryBuilder().From.Admins().ToSql(),
new QueryBuilder().From.Account().ToSql(),
+ new QueryBuilder().From.ActionBatch().ToSql(),
new QueryBuilder().From.AllViewPkPlayers().ToSql(),
+ new QueryBuilder().From.EqualityOrder().ToSql(),
+ new QueryBuilder().From.EqualityPerson().ToSql(),
+ new QueryBuilder().From.EqualityProduct().ToSql(),
new QueryBuilder().From.ExampleData().ToSql(),
new QueryBuilder().From.FindWhereTest().ToSql(),
new QueryBuilder().From.IenumerableAdminsFromFilter().ToSql(),
new QueryBuilder().From.IenumerablePlayersFromIter().ToSql(),
new QueryBuilder().From.IenumerablePlayersWithLevels().ToSql(),
+ new QueryBuilder().From.LogEntry().ToSql(),
new QueryBuilder().From.MyAccount().ToSql(),
new QueryBuilder().From.MyAccountMissing().ToSql(),
new QueryBuilder().From.MyLog().ToSql(),
@@ -580,6 +591,7 @@ public sealed class QueryBuilder
new QueryBuilder().From.NullableVec().ToSql(),
new QueryBuilder().From.NullableVecView().ToSql(),
new QueryBuilder().From.Player().ToSql(),
+ new QueryBuilder().From.PlayerAction().ToSql(),
new QueryBuilder().From.PlayerLevel().ToSql(),
new QueryBuilder().From.PlayersAtLevelOne().ToSql(),
new QueryBuilder().From.RetryLog().ToSql(),
@@ -609,12 +621,17 @@ public sealed class From
{
public global::SpacetimeDB.Table Admins() => new("admins", new AdminsCols("admins"), new AdminsIxCols("admins"));
public global::SpacetimeDB.Table Account() => new("account", new AccountCols("account"), new AccountIxCols("account"));
+ public global::SpacetimeDB.Table ActionBatch() => new("action_batch", new ActionBatchCols("action_batch"), new ActionBatchIxCols("action_batch"));
public global::SpacetimeDB.Table AllViewPkPlayers() => new("all_view_pk_players", new AllViewPkPlayersCols("all_view_pk_players"), new AllViewPkPlayersIxCols("all_view_pk_players"));
+ public global::SpacetimeDB.Table EqualityOrder() => new("equality_order", new EqualityOrderCols("equality_order"), new EqualityOrderIxCols("equality_order"));
+ public global::SpacetimeDB.Table EqualityPerson() => new("equality_person", new EqualityPersonCols("equality_person"), new EqualityPersonIxCols("equality_person"));
+ public global::SpacetimeDB.Table EqualityProduct() => new("equality_product", new EqualityProductCols("equality_product"), new EqualityProductIxCols("equality_product"));
public global::SpacetimeDB.Table ExampleData() => new("example_data", new ExampleDataCols("example_data"), new ExampleDataIxCols("example_data"));
public global::SpacetimeDB.Table FindWhereTest() => new("find_where_test", new FindWhereTestCols("find_where_test"), new FindWhereTestIxCols("find_where_test"));
public global::SpacetimeDB.Table IenumerableAdminsFromFilter() => new("ienumerable_admins_from_filter", new IenumerableAdminsFromFilterCols("ienumerable_admins_from_filter"), new IenumerableAdminsFromFilterIxCols("ienumerable_admins_from_filter"));
public global::SpacetimeDB.Table IenumerablePlayersFromIter() => new("ienumerable_players_from_iter", new IenumerablePlayersFromIterCols("ienumerable_players_from_iter"), new IenumerablePlayersFromIterIxCols("ienumerable_players_from_iter"));
public global::SpacetimeDB.Table IenumerablePlayersWithLevels() => new("ienumerable_players_with_levels", new IenumerablePlayersWithLevelsCols("ienumerable_players_with_levels"), new IenumerablePlayersWithLevelsIxCols("ienumerable_players_with_levels"));
+ public global::SpacetimeDB.Table LogEntry() => new("log_entry", new LogEntryCols("log_entry"), new LogEntryIxCols("log_entry"));
public global::SpacetimeDB.Table MyAccount() => new("my_account", new MyAccountCols("my_account"), new MyAccountIxCols("my_account"));
public global::SpacetimeDB.Table MyAccountMissing() => new("my_account_missing", new MyAccountMissingCols("my_account_missing"), new MyAccountMissingIxCols("my_account_missing"));
public global::SpacetimeDB.Table MyLog() => new("my_log", new MyLogCols("my_log"), new MyLogIxCols("my_log"));
@@ -625,6 +642,7 @@ public sealed class From
public global::SpacetimeDB.Table NullableVec() => new("nullable_vec", new NullableVecCols("nullable_vec"), new NullableVecIxCols("nullable_vec"));
public global::SpacetimeDB.Table NullableVecView() => new("nullable_vec_view", new NullableVecViewCols("nullable_vec_view"), new NullableVecViewIxCols("nullable_vec_view"));
public global::SpacetimeDB.Table Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player"));
+ public global::SpacetimeDB.Table PlayerAction() => new("player_action", new PlayerActionCols("player_action"), new PlayerActionIxCols("player_action"));
public global::SpacetimeDB.Table PlayerLevel() => new("player_level", new PlayerLevelCols("player_level"), new PlayerLevelIxCols("player_level"));
public global::SpacetimeDB.Table PlayersAtLevelOne() => new("players_at_level_one", new PlayersAtLevelOneCols("players_at_level_one"), new PlayersAtLevelOneIxCols("players_at_level_one"));
public global::SpacetimeDB.Table RetryLog() => new("retry_log", new RetryLogCols("retry_log"), new RetryLogIxCols("retry_log"));
@@ -728,6 +746,9 @@ protected override bool Dispatch(IReducerEventContext context, Reducer reducer)
return reducer switch
{
Reducer.Add args => Reducers.InvokeAdd(eventContext, args),
+ Reducer.AddEqualityOrder args => Reducers.InvokeAddEqualityOrder(eventContext, args),
+ Reducer.AddEqualityPerson args => Reducers.InvokeAddEqualityPerson(eventContext, args),
+ Reducer.AddEqualityProduct args => Reducers.InvokeAddEqualityProduct(eventContext, args),
Reducer.Delete args => Reducers.InvokeDelete(eventContext, args),
Reducer.EmitTestEvent args => Reducers.InvokeEmitTestEvent(eventContext, args),
Reducer.InsertEmptyStringIntoNonNullable args => Reducers.InvokeInsertEmptyStringIntoNonNullable(eventContext, args),
@@ -739,7 +760,16 @@ protected override bool Dispatch(IReducerEventContext context, Reducer reducer)
Reducer.InsertViewPkPlayer args => Reducers.InvokeInsertViewPkPlayer(eventContext, args),
Reducer.InsertWhereTest args => Reducers.InvokeInsertWhereTest(eventContext, args),
Reducer.Noop args => Reducers.InvokeNoop(eventContext, args),
+ Reducer.RunAllEqualityTests args => Reducers.InvokeRunAllEqualityTests(eventContext, args),
+ Reducer.RunComplexEqualityTests args => Reducers.InvokeRunComplexEqualityTests(eventContext, args),
+ Reducer.RunEnumEqualityTests args => Reducers.InvokeRunEnumEqualityTests(eventContext, args),
+ Reducer.RunEqualityTests args => Reducers.InvokeRunEqualityTests(eventContext, args),
+ Reducer.ScheduleTask args => Reducers.InvokeScheduleTask(eventContext, args),
Reducer.SetNullableVec args => Reducers.InvokeSetNullableVec(eventContext, args),
+ Reducer.TestListOfNullableSumTypes args => Reducers.InvokeTestListOfNullableSumTypes(eventContext, args),
+ Reducer.TestReducerWithSumTypeParam args => Reducers.InvokeTestReducerWithSumTypeParam(eventContext, args),
+ Reducer.TestSumTypeEquality args => Reducers.InvokeTestSumTypeEquality(eventContext, args),
+ Reducer.TestTableWithoutPrimaryKey args => Reducers.InvokeTestTableWithoutPrimaryKey(eventContext, args),
Reducer.ThrowError args => Reducers.InvokeThrowError(eventContext, args),
Reducer.UpdateViewPkPlayer args => Reducers.InvokeUpdateViewPkPlayer(eventContext, args),
Reducer.UpdateWhereTest args => Reducers.InvokeUpdateWhereTest(eventContext, args),
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ActionBatch.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ActionBatch.g.cs
new file mode 100644
index 00000000000..2fe63f719b5
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ActionBatch.g.cs
@@ -0,0 +1,61 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.BSATN;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteTables
+ {
+ public sealed class ActionBatchHandle : RemoteTableHandle
+ {
+ protected override string RemoteTableName => "action_batch";
+
+ public sealed class IdUniqueIndex : UniqueIndexBase
+ {
+ protected override uint GetKey(ActionBatch row) => row.Id;
+
+ public IdUniqueIndex(ActionBatchHandle table) : base(table) { }
+ }
+
+ public readonly IdUniqueIndex Id;
+
+ internal ActionBatchHandle(DbConnection conn) : base(conn)
+ {
+ Id = new(this);
+ }
+
+ protected override object GetPrimaryKey(ActionBatch row) => row.Id;
+ }
+
+ public readonly ActionBatchHandle ActionBatch;
+ }
+
+ public sealed class ActionBatchCols
+ {
+ public global::SpacetimeDB.Col Id { get; }
+ public global::SpacetimeDB.Col> Actions { get; }
+
+ public ActionBatchCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.Col(tableName, "id");
+ Actions = new global::SpacetimeDB.Col>(tableName, "actions");
+ }
+ }
+
+ public sealed class ActionBatchIxCols
+ {
+ public global::SpacetimeDB.IxCol Id { get; }
+
+ public ActionBatchIxCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.IxCol(tableName, "id");
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityOrder.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityOrder.g.cs
new file mode 100644
index 00000000000..578a800503e
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityOrder.g.cs
@@ -0,0 +1,63 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.BSATN;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteTables
+ {
+ public sealed class EqualityOrderHandle : RemoteTableHandle
+ {
+ protected override string RemoteTableName => "equality_order";
+
+ public sealed class IdUniqueIndex : UniqueIndexBase
+ {
+ protected override uint GetKey(EqualityOrder row) => row.Id;
+
+ public IdUniqueIndex(EqualityOrderHandle table) : base(table) { }
+ }
+
+ public readonly IdUniqueIndex Id;
+
+ internal EqualityOrderHandle(DbConnection conn) : base(conn)
+ {
+ Id = new(this);
+ }
+
+ protected override object GetPrimaryKey(EqualityOrder row) => row.Id;
+ }
+
+ public readonly EqualityOrderHandle EqualityOrder;
+ }
+
+ public sealed class EqualityOrderCols
+ {
+ public global::SpacetimeDB.Col Id { get; }
+ public global::SpacetimeDB.Col CustomerName { get; }
+ public global::SpacetimeDB.Col> Items { get; }
+
+ public EqualityOrderCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.Col(tableName, "id");
+ CustomerName = new global::SpacetimeDB.Col(tableName, "customer_name");
+ Items = new global::SpacetimeDB.Col>(tableName, "items");
+ }
+ }
+
+ public sealed class EqualityOrderIxCols
+ {
+ public global::SpacetimeDB.IxCol Id { get; }
+
+ public EqualityOrderIxCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.IxCol(tableName, "id");
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityPerson.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityPerson.g.cs
new file mode 100644
index 00000000000..81f13df310c
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityPerson.g.cs
@@ -0,0 +1,61 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.BSATN;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteTables
+ {
+ public sealed class EqualityPersonHandle : RemoteTableHandle
+ {
+ protected override string RemoteTableName => "equality_person";
+
+ public sealed class IdUniqueIndex : UniqueIndexBase
+ {
+ protected override uint GetKey(EqualityPerson row) => row.Id;
+
+ public IdUniqueIndex(EqualityPersonHandle table) : base(table) { }
+ }
+
+ public readonly IdUniqueIndex Id;
+
+ internal EqualityPersonHandle(DbConnection conn) : base(conn)
+ {
+ Id = new(this);
+ }
+
+ protected override object GetPrimaryKey(EqualityPerson row) => row.Id;
+ }
+
+ public readonly EqualityPersonHandle EqualityPerson;
+ }
+
+ public sealed class EqualityPersonCols
+ {
+ public global::SpacetimeDB.Col Id { get; }
+ public global::SpacetimeDB.Col Name { get; }
+
+ public EqualityPersonCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.Col(tableName, "id");
+ Name = new global::SpacetimeDB.Col(tableName, "name");
+ }
+ }
+
+ public sealed class EqualityPersonIxCols
+ {
+ public global::SpacetimeDB.IxCol Id { get; }
+
+ public EqualityPersonIxCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.IxCol(tableName, "id");
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityProduct.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityProduct.g.cs
new file mode 100644
index 00000000000..cf212310838
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/EqualityProduct.g.cs
@@ -0,0 +1,65 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.BSATN;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteTables
+ {
+ public sealed class EqualityProductHandle : RemoteTableHandle
+ {
+ protected override string RemoteTableName => "equality_product";
+
+ public sealed class IdUniqueIndex : UniqueIndexBase
+ {
+ protected override uint GetKey(EqualityProduct row) => row.Id;
+
+ public IdUniqueIndex(EqualityProductHandle table) : base(table) { }
+ }
+
+ public readonly IdUniqueIndex Id;
+
+ internal EqualityProductHandle(DbConnection conn) : base(conn)
+ {
+ Id = new(this);
+ }
+
+ protected override object GetPrimaryKey(EqualityProduct row) => row.Id;
+ }
+
+ public readonly EqualityProductHandle EqualityProduct;
+ }
+
+ public sealed class EqualityProductCols
+ {
+ public global::SpacetimeDB.Col Id { get; }
+ public global::SpacetimeDB.Col Name { get; }
+ public global::SpacetimeDB.Col Price { get; }
+ public global::SpacetimeDB.Col Quantity { get; }
+
+ public EqualityProductCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.Col(tableName, "id");
+ Name = new global::SpacetimeDB.Col(tableName, "name");
+ Price = new global::SpacetimeDB.Col(tableName, "price");
+ Quantity = new global::SpacetimeDB.Col(tableName, "quantity");
+ }
+ }
+
+ public sealed class EqualityProductIxCols
+ {
+ public global::SpacetimeDB.IxCol Id { get; }
+
+ public EqualityProductIxCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.IxCol(tableName, "id");
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/LogEntry.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/LogEntry.g.cs
new file mode 100644
index 00000000000..28d5323db9d
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/LogEntry.g.cs
@@ -0,0 +1,47 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.BSATN;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteTables
+ {
+ public sealed class LogEntryHandle : RemoteTableHandle
+ {
+ protected override string RemoteTableName => "log_entry";
+
+ internal LogEntryHandle(DbConnection conn) : base(conn)
+ {
+ }
+ }
+
+ public readonly LogEntryHandle LogEntry;
+ }
+
+ public sealed class LogEntryCols
+ {
+ public global::SpacetimeDB.Col Message { get; }
+ public global::SpacetimeDB.Col Timestamp { get; }
+
+ public LogEntryCols(string tableName)
+ {
+ Message = new global::SpacetimeDB.Col(tableName, "message");
+ Timestamp = new global::SpacetimeDB.Col(tableName, "timestamp");
+ }
+ }
+
+ public sealed class LogEntryIxCols
+ {
+
+ public LogEntryIxCols(string tableName)
+ {
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerAction.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerAction.g.cs
new file mode 100644
index 00000000000..5c4106a31e1
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/PlayerAction.g.cs
@@ -0,0 +1,61 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using SpacetimeDB.BSATN;
+using SpacetimeDB.ClientApi;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ public sealed partial class RemoteTables
+ {
+ public sealed class PlayerActionHandle : RemoteTableHandle
+ {
+ protected override string RemoteTableName => "player_action";
+
+ public sealed class IdUniqueIndex : UniqueIndexBase
+ {
+ protected override uint GetKey(PlayerAction row) => row.Id;
+
+ public IdUniqueIndex(PlayerActionHandle table) : base(table) { }
+ }
+
+ public readonly IdUniqueIndex Id;
+
+ internal PlayerActionHandle(DbConnection conn) : base(conn)
+ {
+ Id = new(this);
+ }
+
+ protected override object GetPrimaryKey(PlayerAction row) => row.Id;
+ }
+
+ public readonly PlayerActionHandle PlayerAction;
+ }
+
+ public sealed class PlayerActionCols
+ {
+ public global::SpacetimeDB.Col Id { get; }
+ public global::SpacetimeDB.Col Action { get; }
+
+ public PlayerActionCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.Col(tableName, "id");
+ Action = new global::SpacetimeDB.Col(tableName, "action");
+ }
+ }
+
+ public sealed class PlayerActionIxCols
+ {
+ public global::SpacetimeDB.IxCol Id { get; }
+
+ public PlayerActionIxCols(string tableName)
+ {
+ Id = new global::SpacetimeDB.IxCol(tableName, "id");
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ActionBatch.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ActionBatch.g.cs
new file mode 100644
index 00000000000..952027c0845
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ActionBatch.g.cs
@@ -0,0 +1,35 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class ActionBatch
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "actions")]
+ public System.Collections.Generic.List Actions;
+
+ public ActionBatch(
+ uint Id,
+ System.Collections.Generic.List Actions
+ )
+ {
+ this.Id = Id;
+ this.Actions = Actions;
+ }
+
+ public ActionBatch()
+ {
+ this.Actions = new();
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityOrder.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityOrder.g.cs
new file mode 100644
index 00000000000..da03f8150da
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityOrder.g.cs
@@ -0,0 +1,38 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class EqualityOrder
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "customer_name")]
+ public string? CustomerName;
+ [DataMember(Name = "items")]
+ public System.Collections.Generic.List? Items;
+
+ public EqualityOrder(
+ uint Id,
+ string? CustomerName,
+ System.Collections.Generic.List? Items
+ )
+ {
+ this.Id = Id;
+ this.CustomerName = CustomerName;
+ this.Items = Items;
+ }
+
+ public EqualityOrder()
+ {
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityPerson.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityPerson.g.cs
new file mode 100644
index 00000000000..a588c9c995a
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityPerson.g.cs
@@ -0,0 +1,35 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class EqualityPerson
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "name")]
+ public string Name;
+
+ public EqualityPerson(
+ uint Id,
+ string Name
+ )
+ {
+ this.Id = Id;
+ this.Name = Name;
+ }
+
+ public EqualityPerson()
+ {
+ this.Name = "";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityProduct.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityProduct.g.cs
new file mode 100644
index 00000000000..cc826acd816
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/EqualityProduct.g.cs
@@ -0,0 +1,43 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class EqualityProduct
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "name")]
+ public string Name;
+ [DataMember(Name = "price")]
+ public int Price;
+ [DataMember(Name = "quantity")]
+ public int Quantity;
+
+ public EqualityProduct(
+ uint Id,
+ string Name,
+ int Price,
+ int Quantity
+ )
+ {
+ this.Id = Id;
+ this.Name = Name;
+ this.Price = Price;
+ this.Quantity = Quantity;
+ }
+
+ public EqualityProduct()
+ {
+ this.Name = "";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/GameAction.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/GameAction.g.cs
new file mode 100644
index 00000000000..2b15c0509ba
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/GameAction.g.cs
@@ -0,0 +1,16 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ public partial record GameAction : SpacetimeDB.TaggedEnum<(
+ string Move,
+ string Attack,
+ int Defend
+ )>;
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/LogEntry.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/LogEntry.g.cs
new file mode 100644
index 00000000000..7f16512c2ee
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/LogEntry.g.cs
@@ -0,0 +1,35 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class LogEntry
+ {
+ [DataMember(Name = "message")]
+ public string Message;
+ [DataMember(Name = "timestamp")]
+ public ulong Timestamp;
+
+ public LogEntry(
+ string Message,
+ ulong Timestamp
+ )
+ {
+ this.Message = Message;
+ this.Timestamp = Timestamp;
+ }
+
+ public LogEntry()
+ {
+ this.Message = "";
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerAction.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerAction.g.cs
new file mode 100644
index 00000000000..cdc7cb63447
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/PlayerAction.g.cs
@@ -0,0 +1,35 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class PlayerAction
+ {
+ [DataMember(Name = "id")]
+ public uint Id;
+ [DataMember(Name = "action")]
+ public GameAction Action;
+
+ public PlayerAction(
+ uint Id,
+ GameAction Action
+ )
+ {
+ this.Id = Id;
+ this.Action = Action;
+ }
+
+ public PlayerAction()
+ {
+ this.Action = null!;
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ProductItem.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ProductItem.g.cs
new file mode 100644
index 00000000000..4eed53dd688
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ProductItem.g.cs
@@ -0,0 +1,34 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class ProductItem
+ {
+ [DataMember(Name = "product_id")]
+ public uint ProductId;
+ [DataMember(Name = "quantity")]
+ public int Quantity;
+
+ public ProductItem(
+ uint ProductId,
+ int Quantity
+ )
+ {
+ this.ProductId = ProductId;
+ this.Quantity = Quantity;
+ }
+
+ public ProductItem()
+ {
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ScheduledTask.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ScheduledTask.g.cs
new file mode 100644
index 00000000000..e6147d36409
--- /dev/null
+++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/ScheduledTask.g.cs
@@ -0,0 +1,40 @@
+// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
+// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace SpacetimeDB.Types
+{
+ [SpacetimeDB.Type]
+ [DataContract]
+ public sealed partial class ScheduledTask
+ {
+ [DataMember(Name = "id")]
+ public ulong Id;
+ [DataMember(Name = "task_name")]
+ public string TaskName;
+ [DataMember(Name = "scheduled_at")]
+ public SpacetimeDB.ScheduleAt ScheduledAt;
+
+ public ScheduledTask(
+ ulong Id,
+ string TaskName,
+ SpacetimeDB.ScheduleAt ScheduledAt
+ )
+ {
+ this.Id = Id;
+ this.TaskName = TaskName;
+ this.ScheduledAt = ScheduledAt;
+ }
+
+ public ScheduledTask()
+ {
+ this.TaskName = "";
+ this.ScheduledAt = null!;
+ }
+ }
+}
diff --git a/sdks/csharp/examples~/regression-tests/server/Lib.cs b/sdks/csharp/examples~/regression-tests/server/Lib.cs
index 9e85e13ad65..0644115876c 100644
--- a/sdks/csharp/examples~/regression-tests/server/Lib.cs
+++ b/sdks/csharp/examples~/regression-tests/server/Lib.cs
@@ -201,6 +201,93 @@ public partial struct ViewPkMembershipSecondary
public ulong PlayerId;
}
+ // === Equality Test Tables and Types ===
+
+ // Struct for testing struct equality (used in Product and Order)
+ [SpacetimeDB.Type]
+ public partial struct ProductItem
+ {
+ public uint ProductId;
+ public int Quantity;
+ }
+
+ // Simple struct - basic value type equality testing
+ [SpacetimeDB.Table(Accessor = "equality_person", Public = true)]
+ public partial struct EqualityPerson
+ {
+ [SpacetimeDB.PrimaryKey]
+ public uint Id;
+ public string Name;
+ }
+
+ // Complex struct with multiple fields for equality testing
+ [SpacetimeDB.Table(Accessor = "equality_product", Public = true)]
+ public partial struct EqualityProduct
+ {
+ [SpacetimeDB.PrimaryKey]
+ public uint Id;
+ public string Name;
+ public int Price; // Price in cents
+ public int Quantity;
+ }
+
+ // Record with reference type fields for equality testing
+ [SpacetimeDB.Table(Accessor = "equality_order", Public = true)]
+ public partial record EqualityOrder
+ {
+ [SpacetimeDB.PrimaryKey]
+ public uint Id;
+ public string? CustomerName;
+ public List? Items;
+ }
+
+ // Enum for equality testing
+ public enum TestStatus
+ {
+ Pending,
+ Active,
+ Completed
+ }
+
+ // Custom sum type for testing TaggedEnum equality
+ [SpacetimeDB.Type]
+ public partial record GameAction : TaggedEnum<(string Move, string Attack, int Defend)> { }
+
+ [SpacetimeDB.Table(Accessor = "player_action", Public = true)]
+ public partial struct PlayerAction
+ {
+ [SpacetimeDB.PrimaryKey]
+ public uint Id;
+ public GameAction Action;
+ }
+
+ // List of nullable sum types - tests nullable suffix handling
+ [SpacetimeDB.Table(Accessor = "action_batch", Public = true)]
+ public partial struct ActionBatch
+ {
+ [SpacetimeDB.PrimaryKey]
+ public uint Id;
+ public List Actions;
+ }
+
+ // Table WITHOUT primary key - triggers RawIndexDefV10.Equals/GetHashCode path
+ [SpacetimeDB.Table(Accessor = "log_entry", Public = true)]
+ public partial struct LogEntry
+ {
+ public string Message;
+ public ulong Timestamp;
+ }
+
+ // Scheduled table - tests ScheduleAt sum type (manually-written, uses ReferenceUse)
+ [SpacetimeDB.Table(Accessor = "scheduled_task", Scheduled = "ExecuteScheduledTask", ScheduledAt = "ScheduledAt")]
+ public partial struct ScheduledTask
+ {
+ [SpacetimeDB.PrimaryKey]
+ public ulong Id;
+ public string TaskName;
+ public ScheduleAt ScheduledAt;
+ }
+
// At-most-one row: return T?
[SpacetimeDB.View(Accessor = "my_player", Public = true)]
public static Player? MyPlayer(ViewContext ctx)
@@ -882,6 +969,255 @@ public static void EmitTestEvent(ReducerContext ctx, string name, ulong value)
[SpacetimeDB.Reducer]
public static void Noop(ReducerContext ctx) { }
+ // === Equality Test Reducers ===
+
+ [SpacetimeDB.Reducer]
+ public static void AddEqualityPerson(ReducerContext ctx, uint id, string name)
+ {
+ ctx.Db.equality_person.Insert(new EqualityPerson { Id = id, Name = name });
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void AddEqualityProduct(ReducerContext ctx, uint id, string name, int price, int quantity)
+ {
+ ctx.Db.equality_product.Insert(new EqualityProduct { Id = id, Name = name, Price = price, Quantity = quantity });
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void AddEqualityOrder(ReducerContext ctx, uint id, string? customerName, List? items)
+ {
+ ctx.Db.equality_order.Insert(new EqualityOrder { Id = id, CustomerName = customerName, Items = items });
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void RunEqualityTests(ReducerContext ctx)
+ {
+ Log.Info("=== Testing Equality ===");
+
+ // Test 1: Direct string comparison (no allocation)
+ bool stringEqual = "Alice" == "Alice";
+ Log.Info($"Test 1 - String equality: {stringEqual}");
+
+ // Test 2: Enum equality (no allocation)
+ var s1 = TestStatus.Pending;
+ var s2 = TestStatus.Pending;
+ bool enumEqual = s1 == s2;
+ Log.Info($"Test 2 - Enum equality: {enumEqual}");
+
+ // Test 3: Integer equality (no allocation)
+ int i1 = 42;
+ int i2 = 42;
+ bool intEqual = i1 == i2;
+ Log.Info($"Test 3 - Int equality: {intEqual}");
+
+ Log.Info("=== Equality Tests Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void RunComplexEqualityTests(ReducerContext ctx)
+ {
+ Log.Info("=== Testing Complex Equality ===");
+
+ // Test struct .Equals() - this used to cause boxing/allocation
+ var item1 = new ProductItem { ProductId = 1, Quantity = 10 };
+ var item2 = new ProductItem { ProductId = 1, Quantity = 10 };
+ var item3 = new ProductItem { ProductId = 2, Quantity = 5 };
+
+ bool structEqual = item1.Equals(item2);
+ bool structNotEqual = item1.Equals(item3);
+ Log.Info($"Struct equality (same): {structEqual}");
+ Log.Info($"Struct equality (different): {structNotEqual}");
+
+ // Test Person struct equality (table type)
+ var person1 = new EqualityPerson { Id = 100, Name = "Test" };
+ var person2 = new EqualityPerson { Id = 100, Name = "Test" };
+ var person3 = new EqualityPerson { Id = 200, Name = "Other" };
+
+ bool personEqual = person1.Equals(person2);
+ bool personNotEqual = person1.Equals(person3);
+ Log.Info($"Person struct equality (same): {personEqual}");
+ Log.Info($"Person struct equality (different): {personNotEqual}");
+
+ Log.Info("=== Complex Equality Tests Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void RunEnumEqualityTests(ReducerContext ctx)
+ {
+ var s1 = TestStatus.Pending;
+ var s2 = TestStatus.Pending;
+ var s3 = TestStatus.Active;
+
+ bool equal1 = s1 == s2; // Should be true
+ bool equal2 = s1 == s3; // Should be false
+
+ Log.Info($"Enum equality (same): {equal1}");
+ Log.Info($"Enum equality (different): {equal2}");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void TestTableWithoutPrimaryKey(ReducerContext ctx)
+ {
+ Log.Info("=== Testing Table WITHOUT Primary Key (RawIndexDefV10 path) ===");
+
+ // This triggers RawIndexDefV10.Equals/GetHashCode which previously failed
+ ctx.Db.log_entry.Insert(new LogEntry { Message = "Test 1", Timestamp = 1000 });
+ ctx.Db.log_entry.Insert(new LogEntry { Message = "Test 2", Timestamp = 2000 });
+
+ int count = 0;
+ foreach (var entry in ctx.Db.log_entry.Iter())
+ {
+ count++;
+ Log.Info($"LogEntry: {entry.Message} at {entry.Timestamp}");
+ }
+
+ Log.Info($"Total log entries: {count}");
+ Log.Info("=== Table Without Primary Key Test Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void TestSumTypeEquality(ReducerContext ctx)
+ {
+ Log.Info("=== Testing Sum Type (TaggedEnum) Equality ===");
+
+ // Test sum type equality via generated Equals
+ var action1 = new GameAction.Move("North");
+ var action2 = new GameAction.Move("North");
+ var action3 = new GameAction.Attack("Sword");
+
+ bool equal = action1.Equals(action2);
+ bool notEqual = action1.Equals(action3);
+
+ Log.Info($"Sum type equality (same variant): {equal}");
+ Log.Info($"Sum type equality (different variant): {!notEqual}");
+
+ // Insert and retrieve sum type from table
+ ctx.Db.player_action.Insert(new PlayerAction { Id = 1, Action = action1 });
+ ctx.Db.player_action.Insert(new PlayerAction { Id = 2, Action = action3 });
+
+ foreach (var pa in ctx.Db.player_action.Iter())
+ {
+ var desc = pa.Action switch
+ {
+ GameAction.Move(var m) => $"Move: {m}",
+ GameAction.Attack(var a) => $"Attack: {a}",
+ GameAction.Defend(var d) => $"Defend: {d}",
+ _ => "Unknown"
+ };
+ Log.Info($"PlayerAction {pa.Id}: {desc}");
+ }
+
+ Log.Info("=== Sum Type Equality Test Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void TestListOfNullableSumTypes(ReducerContext ctx)
+ {
+ Log.Info("=== Testing List of Nullable Sum Types ===");
+
+ // This exercises SumTypeUse with nullable suffix (GameAction?)
+ var actions = new List
+ {
+ new GameAction.Move("North"),
+ null,
+ new GameAction.Attack("Bow"),
+ null
+ };
+
+ ctx.Db.action_batch.Insert(new ActionBatch { Id = 1, Actions = actions });
+
+ foreach (var batch in ctx.Db.action_batch.Iter())
+ {
+ Log.Info($"Batch {batch.Id} has {batch.Actions?.Count ?? 0} actions");
+ if (batch.Actions != null)
+ {
+ for (int i = 0; i < batch.Actions.Count; i++)
+ {
+ var desc = batch.Actions[i] switch
+ {
+ null => "null",
+ GameAction.Move(var m) => $"Move({m})",
+ GameAction.Attack(var a) => $"Attack({a})",
+ GameAction.Defend(var d) => $"Defend({d})",
+ _ => "Unknown"
+ };
+ Log.Info($" Action {i}: {desc}");
+ }
+ }
+ }
+
+ Log.Info("=== List of Nullable Sum Types Test Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void ExecuteScheduledTask(ReducerContext ctx, ScheduledTask task)
+ {
+ Log.Info($"Executing scheduled task: {task.TaskName}");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void ScheduleTask(ReducerContext ctx, ulong delayMicros)
+ {
+ Log.Info("=== Testing Scheduled Table (ScheduleAt sum type) ===");
+
+ // ScheduleAt is a manually-written sum type - tests ReferenceUse path
+ var scheduledAt = ScheduleAt.TimeSpanFromMicroseconds((long)delayMicros);
+
+ ctx.Db.scheduled_task.Insert(new ScheduledTask
+ {
+ Id = 1,
+ TaskName = "Test Task",
+ ScheduledAt = scheduledAt
+ });
+
+ Log.Info($"Scheduled task for {delayMicros} microseconds from now");
+ Log.Info("=== Scheduled Table Test Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void TestReducerWithSumTypeParam(ReducerContext ctx, GameAction action)
+ {
+ Log.Info("=== Testing Reducer with Sum Type Parameter ===");
+
+ // This exercises SumTypeUse.EqualsStatement for parameter comparison
+ var match = action switch
+ {
+ GameAction.Move(var m) => $"Moving: {m}",
+ GameAction.Attack(var a) => $"Attacking with: {a}",
+ GameAction.Defend(var d) => $"Defending with power: {d}",
+ _ => "Unknown action"
+ };
+
+ Log.Info(match);
+ Log.Info("=== Reducer with Sum Type Parameter Test Complete ===");
+ }
+
+ [SpacetimeDB.Reducer]
+ public static void RunAllEqualityTests(ReducerContext ctx)
+ {
+ Log.Info("========== Starting Equality Tests ==========");
+
+ // Insert test data
+ ctx.Db.equality_person.Insert(new EqualityPerson { Id = 1, Name = "Alice" });
+ ctx.Db.equality_product.Insert(new EqualityProduct { Id = 1, Name = "Widget", Price = 999, Quantity = 100 });
+
+ // Run tests
+ RunEqualityTests(ctx);
+ RunComplexEqualityTests(ctx);
+ RunEnumEqualityTests(ctx);
+
+ // New tests for sum type fixes
+ TestTableWithoutPrimaryKey(ctx);
+ TestSumTypeEquality(ctx);
+ TestListOfNullableSumTypes(ctx);
+ ScheduleTask(ctx, 1000000); // 1 second delay
+
+ // Test reducer with sum type parameter
+ TestReducerWithSumTypeParam(ctx, new GameAction.Attack("Magic Sword"));
+
+ Log.Info("========== All Equality Tests Complete ==========");
+ }
+
[SpacetimeDB.Procedure]
public static void InsertWithTxPanic(ProcedureContext ctx)
{
diff --git a/sdks/csharp/tools~/write-nuget-config.sh b/sdks/csharp/tools~/write-nuget-config.sh
index 9997cd219ff..69286284ad8 100755
--- a/sdks/csharp/tools~/write-nuget-config.sh
+++ b/sdks/csharp/tools~/write-nuget-config.sh
@@ -15,6 +15,8 @@ cat >NuGet.Config <
+
+
@@ -30,6 +32,11 @@ cat >NuGet.Config <
+
+
+
+
+
@@ -43,6 +50,8 @@ cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" <
+
+
@@ -58,6 +67,11 @@ cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" <
+
+
+
+
+