Repository: clockworklabs/SpacetimeDB Branch: master Commit: 31b9af3da35f Files: 5165 Total size: 32.0 MB Directory structure: gitextract_6scv2pvq/ ├── .cargo/ │ └── config.toml ├── .dockerignore ├── .envrc ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── Dockerfile │ ├── GREMLINS.md │ ├── docker-compose.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── attach-artifacts.yml │ ├── benchmarks.yml │ ├── check-merge-labels.yml │ ├── check-pr-base.yml │ ├── ci.yml │ ├── discord-posts.yml │ ├── docker.yml │ ├── docs-publish.yaml │ ├── docs-test.yaml │ ├── llm-benchmark-update.yml │ ├── package.yml │ ├── pr_approval_check.yml │ ├── rust_matcher.json │ ├── tag-release.yml │ ├── typescript-lint.yml │ ├── typescript-test.yml │ └── upgrade-version-check.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .rustfmt.toml ├── Cargo.toml ├── Dockerfile ├── LICENSE.txt ├── README.md ├── clippy.toml ├── crates/ │ ├── auth/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── identity.rs │ │ └── lib.rs │ ├── bench/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── benches/ │ │ │ ├── callgrind.rs │ │ │ ├── delete_table.rs │ │ │ ├── generic.rs │ │ │ ├── index.rs │ │ │ ├── special.rs │ │ │ └── subscription.rs │ │ ├── callgrind-docker.sh │ │ ├── clippy.toml │ │ ├── flamegraph.sh │ │ ├── hyper_cmp.py │ │ ├── instruments.sh │ │ └── src/ │ │ ├── database.rs │ │ ├── lib.rs │ │ ├── schemas.rs │ │ ├── spacetime_module.rs │ │ ├── spacetime_raw.rs │ │ └── sqlite.rs │ ├── bindings/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── bindings-doctests.sh │ │ ├── src/ │ │ │ ├── client_visibility_filter.rs │ │ │ ├── http.rs │ │ │ ├── lib.rs │ │ │ ├── log_stopwatch.rs │ │ │ ├── logger.rs │ │ │ ├── rng.rs │ │ │ ├── rt.rs │ │ │ └── table.rs │ │ └── tests/ │ │ ├── deps.rs │ │ ├── snapshots/ │ │ │ ├── deps__duplicate_deps.snap │ │ │ └── deps__spacetimedb_bindings_dependencies.snap │ │ ├── ui/ │ │ │ ├── reducers.rs │ │ │ ├── reducers.stderr │ │ │ ├── tables.rs │ │ │ ├── tables.stderr │ │ │ ├── views-more.rs │ │ │ ├── views-more.stderr │ │ │ ├── views.rs │ │ │ └── views.stderr │ │ └── ui.rs │ ├── bindings-cpp/ │ │ ├── .gitignore │ │ ├── ARCHITECTURE.md │ │ ├── CMakeLists.txt │ │ ├── DEVELOP.md │ │ ├── QUICKSTART.md │ │ ├── README.md │ │ ├── REFERENCE.md │ │ ├── include/ │ │ │ ├── spacetimedb/ │ │ │ │ ├── abi/ │ │ │ │ │ ├── FFI.h │ │ │ │ │ ├── abi.h │ │ │ │ │ └── opaque_types.h │ │ │ │ ├── auth_ctx.h │ │ │ │ ├── bsatn/ │ │ │ │ │ ├── DEVELOP.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── algebraic_type.h │ │ │ │ │ ├── bsatn.h │ │ │ │ │ ├── primitive_traits.h │ │ │ │ │ ├── reader.h │ │ │ │ │ ├── result.h │ │ │ │ │ ├── schedule_at.h │ │ │ │ │ ├── schedule_at_impl.h │ │ │ │ │ ├── serialization.h │ │ │ │ │ ├── size_calculator.h │ │ │ │ │ ├── sum_type.h │ │ │ │ │ ├── time_duration.h │ │ │ │ │ ├── timestamp.h │ │ │ │ │ ├── traits.h │ │ │ │ │ ├── type_extensions.h │ │ │ │ │ ├── types.h │ │ │ │ │ ├── types_impl.h │ │ │ │ │ ├── uuid.h │ │ │ │ │ └── writer.h │ │ │ │ ├── client_visibility_filter.h │ │ │ │ ├── database.h │ │ │ │ ├── enum_macro.h │ │ │ │ ├── error_handling.h │ │ │ │ ├── http.h │ │ │ │ ├── http_client_impl.h │ │ │ │ ├── http_convert.h │ │ │ │ ├── http_wire.h │ │ │ │ ├── index_iterator.h │ │ │ │ ├── internal/ │ │ │ │ │ ├── Module.h │ │ │ │ │ ├── Module_impl.h │ │ │ │ │ ├── README.md │ │ │ │ │ ├── autogen/ │ │ │ │ │ │ ├── AlgebraicType.g.h │ │ │ │ │ │ ├── CaseConversionPolicy.g.h │ │ │ │ │ │ ├── ExplicitNameEntry.g.h │ │ │ │ │ │ ├── ExplicitNames.g.h │ │ │ │ │ │ ├── FunctionVisibility.g.h │ │ │ │ │ │ ├── IndexType.g.h │ │ │ │ │ │ ├── Lifecycle.g.h │ │ │ │ │ │ ├── MiscModuleExport.g.h │ │ │ │ │ │ ├── NameMapping.g.h │ │ │ │ │ │ ├── ProductType.g.h │ │ │ │ │ │ ├── ProductTypeElement.g.h │ │ │ │ │ │ ├── RawColumnDefV8.g.h │ │ │ │ │ │ ├── RawColumnDefaultValueV10.g.h │ │ │ │ │ │ ├── RawColumnDefaultValueV9.g.h │ │ │ │ │ │ ├── RawConstraintDataV9.g.h │ │ │ │ │ │ ├── RawConstraintDefV10.g.h │ │ │ │ │ │ ├── RawConstraintDefV8.g.h │ │ │ │ │ │ ├── RawConstraintDefV9.g.h │ │ │ │ │ │ ├── RawIndexAlgorithm.g.h │ │ │ │ │ │ ├── RawIndexDefV10.g.h │ │ │ │ │ │ ├── RawIndexDefV8.g.h │ │ │ │ │ │ ├── RawIndexDefV9.g.h │ │ │ │ │ │ ├── RawLifeCycleReducerDefV10.g.h │ │ │ │ │ │ ├── RawMiscModuleExportV9.g.h │ │ │ │ │ │ ├── RawModuleDef.g.h │ │ │ │ │ │ ├── RawModuleDefV10.g.h │ │ │ │ │ │ ├── RawModuleDefV10Section.g.h │ │ │ │ │ │ ├── RawModuleDefV8.g.h │ │ │ │ │ │ ├── RawModuleDefV9.g.h │ │ │ │ │ │ ├── RawProcedureDefV10.g.h │ │ │ │ │ │ ├── RawProcedureDefV9.g.h │ │ │ │ │ │ ├── RawReducerDefV10.g.h │ │ │ │ │ │ ├── RawReducerDefV9.g.h │ │ │ │ │ │ ├── RawRowLevelSecurityDefV9.g.h │ │ │ │ │ │ ├── RawScheduleDefV10.g.h │ │ │ │ │ │ ├── RawScheduleDefV9.g.h │ │ │ │ │ │ ├── RawScopedTypeNameV10.g.h │ │ │ │ │ │ ├── RawScopedTypeNameV9.g.h │ │ │ │ │ │ ├── RawSequenceDefV10.g.h │ │ │ │ │ │ ├── RawSequenceDefV8.g.h │ │ │ │ │ │ ├── RawSequenceDefV9.g.h │ │ │ │ │ │ ├── RawTableDefV10.g.h │ │ │ │ │ │ ├── RawTableDefV8.g.h │ │ │ │ │ │ ├── RawTableDefV9.g.h │ │ │ │ │ │ ├── RawTypeDefV10.g.h │ │ │ │ │ │ ├── RawTypeDefV9.g.h │ │ │ │ │ │ ├── RawUniqueConstraintDataV9.g.h │ │ │ │ │ │ ├── RawViewDefV10.g.h │ │ │ │ │ │ ├── RawViewDefV9.g.h │ │ │ │ │ │ ├── ReducerDef.g.h │ │ │ │ │ │ ├── SumType.g.h │ │ │ │ │ │ ├── SumTypeVariant.g.h │ │ │ │ │ │ ├── TableAccess.g.h │ │ │ │ │ │ ├── TableDesc.g.h │ │ │ │ │ │ ├── TableType.g.h │ │ │ │ │ │ ├── TypeAlias.g.h │ │ │ │ │ │ └── Typespace.g.h │ │ │ │ │ ├── autogen_base.h │ │ │ │ │ ├── bsatn_adapters.h │ │ │ │ │ ├── buffer_pool.h │ │ │ │ │ ├── debug.h │ │ │ │ │ ├── field_registration.h │ │ │ │ │ ├── forward_declarations.h │ │ │ │ │ ├── module_type_registration.h │ │ │ │ │ ├── runtime_registration.h │ │ │ │ │ ├── template_utils.h │ │ │ │ │ ├── v10_builder.h │ │ │ │ │ └── v9_builder.h │ │ │ │ ├── jwt_claims.h │ │ │ │ ├── logger.h │ │ │ │ ├── macros.h │ │ │ │ ├── outcome.h │ │ │ │ ├── procedure_context.h │ │ │ │ ├── procedure_macros.h │ │ │ │ ├── random.h │ │ │ │ ├── range_queries.h │ │ │ │ ├── readonly_database_context.h │ │ │ │ ├── readonly_field_accessors.h │ │ │ │ ├── readonly_table_accessor.h │ │ │ │ ├── reducer_context.h │ │ │ │ ├── reducer_error.h │ │ │ │ ├── reducer_macros.h │ │ │ │ ├── schedule_reducer.h │ │ │ │ ├── table.h │ │ │ │ ├── table_with_constraints.h │ │ │ │ ├── tx_context.h │ │ │ │ ├── version.h.in │ │ │ │ ├── view_context.h │ │ │ │ └── view_macros.h │ │ │ └── spacetimedb.h │ │ ├── src/ │ │ │ ├── abi/ │ │ │ │ ├── module_exports.cpp │ │ │ │ └── wasi_shims.cpp │ │ │ └── internal/ │ │ │ ├── AlgebraicType.cpp │ │ │ ├── Module.cpp │ │ │ ├── module_type_registration.cpp │ │ │ ├── v10_builder.cpp │ │ │ └── v9_builder.cpp │ │ └── tests/ │ │ ├── client-comparison/ │ │ │ ├── README.md │ │ │ ├── check_tables.py │ │ │ ├── run_client_comparison.sh │ │ │ └── scripts/ │ │ │ ├── compare_clients.sh │ │ │ ├── compare_modules.sh │ │ │ ├── regenerate_cpp_client.sh │ │ │ └── regenerate_rust_client.sh │ │ └── type-isolation-test/ │ │ ├── .gitignore │ │ ├── CMakeLists.module.txt │ │ ├── CMakeLists.txt │ │ ├── CMakeLists_module.txt │ │ ├── README.md │ │ ├── run_type_isolation_test.sh │ │ ├── test_modules/ │ │ │ ├── debug_constraint_simple.cpp │ │ │ ├── debug_large_struct.cpp │ │ │ ├── debug_minimal_fail.cpp │ │ │ ├── debug_optional_large_struct.cpp │ │ │ ├── debug_simple_enum.cpp │ │ │ ├── debug_special.cpp │ │ │ ├── debug_special_constraints.cpp │ │ │ ├── debug_special_reducers.cpp │ │ │ ├── debug_trace.cpp │ │ │ ├── debug_vector_only.cpp │ │ │ ├── debug_vector_only_simple.cpp │ │ │ ├── error_autoinc_non_integer.cpp │ │ │ ├── error_circular_ref.cpp │ │ │ ├── error_default_missing_field.cpp │ │ │ ├── error_invalid_index.cpp │ │ │ ├── error_multicolumn_missing_field.cpp │ │ │ ├── error_multiple_pk.cpp │ │ │ ├── error_non_spacetimedb_type.cpp │ │ │ ├── error_scheduled_id_pk.cpp │ │ │ ├── module01_basic_unsigned.cpp │ │ │ ├── module02_large_unsigned.cpp │ │ │ ├── module03_basic_signed.cpp │ │ │ ├── module04_large_signed.cpp │ │ │ ├── module05_float_bool.cpp │ │ │ ├── module06_string.cpp │ │ │ ├── module07_special_types.cpp │ │ │ ├── module08_enums.cpp │ │ │ ├── module09_structs.cpp │ │ │ ├── module10_vectors.cpp │ │ │ ├── module11_optional.cpp │ │ │ ├── module12_constraints.cpp │ │ │ ├── test_complex_reducer_only.cpp │ │ │ ├── test_complex_table_only.cpp │ │ │ ├── test_connectionid_only.cpp │ │ │ ├── test_debug_optional.cpp │ │ │ ├── test_enum_table_only.cpp │ │ │ ├── test_enum_vector_payloads.cpp │ │ │ ├── test_identity_only.cpp │ │ │ ├── test_massive_reducer.cpp │ │ │ ├── test_minimal_special.cpp │ │ │ ├── test_mixed_types.cpp │ │ │ ├── test_multicolumn_index_valid.cpp │ │ │ ├── test_nested_optionals.cpp │ │ │ ├── test_optional_debug.cpp │ │ │ ├── test_optional_reducer_only.cpp │ │ │ ├── test_optional_simple.cpp │ │ │ ├── test_optional_table_only.cpp │ │ │ ├── test_simple_table.cpp │ │ │ ├── test_single_special_vector.cpp │ │ │ ├── test_special_minimal.cpp │ │ │ ├── test_special_vectors.cpp │ │ │ ├── test_timeduration_only.cpp │ │ │ ├── test_timestamp_only.cpp │ │ │ ├── test_u128_only.cpp │ │ │ ├── test_unified_enum.cpp │ │ │ ├── test_unit_isolation.cpp │ │ │ ├── test_unit_progression.cpp │ │ │ ├── test_unit_simple.cpp │ │ │ ├── test_unit_struct.cpp │ │ │ └── test_wrapped_special.cpp │ │ └── update_table_from_log.sh │ ├── bindings-csharp/ │ │ ├── .config/ │ │ │ └── dotnet-tools.json │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── BSATN.Codegen/ │ │ │ ├── BSATN.Codegen.csproj │ │ │ ├── Diag.cs │ │ │ ├── Type.cs │ │ │ └── Utils.cs │ │ ├── BSATN.Runtime/ │ │ │ ├── .gitignore │ │ │ ├── Attrs.cs │ │ │ ├── BSATN/ │ │ │ │ ├── AlgebraicType.cs │ │ │ │ ├── I128.cs │ │ │ │ ├── I256.cs │ │ │ │ ├── Runtime.cs │ │ │ │ ├── U128.cs │ │ │ │ ├── U256.cs │ │ │ │ └── Uuid.cs │ │ │ ├── BSATN.Runtime.csproj │ │ │ ├── Builtins.cs │ │ │ ├── Db.cs │ │ │ ├── HttpWireTypes.cs │ │ │ ├── Internal/ │ │ │ │ └── ByteArrayComparer.cs │ │ │ ├── QueryBuilder.cs │ │ │ └── package.json │ │ ├── BSATN.Runtime.Tests/ │ │ │ ├── BSATN.Runtime.Tests.csproj │ │ │ └── Tests.cs │ │ ├── Codegen/ │ │ │ ├── Codegen.csproj │ │ │ ├── Diag.cs │ │ │ ├── Module.cs │ │ │ └── README.md │ │ ├── Codegen.Tests/ │ │ │ ├── Codegen.Tests.csproj │ │ │ ├── TestInit.cs │ │ │ ├── Tests.cs │ │ │ └── fixtures/ │ │ │ ├── Directory.Build.props │ │ │ ├── client/ │ │ │ │ ├── Lib.cs │ │ │ │ ├── client.csproj │ │ │ │ └── snapshots/ │ │ │ │ ├── Type#CustomClass.verified.cs │ │ │ │ ├── Type#CustomStruct.verified.cs │ │ │ │ ├── Type#CustomTaggedEnum.verified.cs │ │ │ │ └── Type#PublicTable.verified.cs │ │ │ ├── diag/ │ │ │ │ ├── .gitattributes │ │ │ │ ├── Lib.cs │ │ │ │ ├── diag.csproj │ │ │ │ └── snapshots/ │ │ │ │ ├── ExtraCompilationErrors.verified.txt │ │ │ │ ├── Module#FFI.verified.cs │ │ │ │ ├── Module#InAnotherNamespace.TestDuplicateTableName.verified.cs │ │ │ │ ├── Module#Player.verified.cs │ │ │ │ ├── Module#Reducers.InAnotherNamespace.TestDuplicateReducerName.verified.cs │ │ │ │ ├── Module#Reducers.OnReducerWithReservedPrefix.verified.cs │ │ │ │ ├── Module#Reducers.TestDuplicateReducerKind1.verified.cs │ │ │ │ ├── Module#Reducers.TestDuplicateReducerKind2.verified.cs │ │ │ │ ├── Module#Reducers.TestDuplicateReducerName.verified.cs │ │ │ │ ├── Module#Reducers.TestReducerReturnType.verified.cs │ │ │ │ ├── Module#Reducers.TestReducerWithoutContext.verified.cs │ │ │ │ ├── Module#Reducers.__ReducerWithReservedPrefix.verified.cs │ │ │ │ ├── Module#TestAutoIncNotInteger.verified.cs │ │ │ │ ├── Module#TestDefaultFieldValues.verified.cs │ │ │ │ ├── Module#TestDuplicateTableName.verified.cs │ │ │ │ ├── Module#TestIndexIssues.verified.cs │ │ │ │ ├── Module#TestScheduleIssues.DummyScheduledReducer.verified.cs │ │ │ │ ├── Module#TestScheduleIssues.verified.cs │ │ │ │ ├── Module#TestTableTaggedEnum.verified.cs │ │ │ │ ├── Module#TestUniqueNotEquatable.verified.cs │ │ │ │ ├── Module.verified.txt │ │ │ │ ├── Type#MyStruct.verified.cs │ │ │ │ ├── Type#TestTaggedEnumField.verified.cs │ │ │ │ ├── Type#TestTaggedEnumInlineTuple.verified.cs │ │ │ │ ├── Type#TestTypeParams_T_.verified.cs │ │ │ │ ├── Type#TestUnsupportedType.verified.cs │ │ │ │ └── Type.verified.txt │ │ │ ├── explicitnames/ │ │ │ │ ├── Lib.cs │ │ │ │ ├── explicitnames.csproj │ │ │ │ └── snapshots/ │ │ │ │ ├── Module#DemoTable.verified.cs │ │ │ │ ├── Module#FFI.verified.cs │ │ │ │ ├── Module#Reducers.DemoProcedure.verified.cs │ │ │ │ ├── Module#Reducers.DemoReducer.verified.cs │ │ │ │ └── Type#DemoType.verified.cs │ │ │ └── server/ │ │ │ ├── Lib.cs │ │ │ ├── server.csproj │ │ │ └── snapshots/ │ │ │ ├── Module#BTreeMultiColumn.verified.cs │ │ │ ├── Module#BTreeViews.verified.cs │ │ │ ├── Module#FFI.verified.cs │ │ │ ├── Module#MultiTableRow.InsertMultiData.verified.cs │ │ │ ├── Module#MultiTableRow.verified.cs │ │ │ ├── Module#PrivateTable.verified.cs │ │ │ ├── Module#PublicTable.verified.cs │ │ │ ├── Module#Reducers.InsertData.verified.cs │ │ │ ├── Module#Reducers.ScheduleImmediate.verified.cs │ │ │ ├── Module#RegressionMultipleUniqueIndexesHadSameName.verified.cs │ │ │ ├── Module#Test.NestingNamespaces.AndClasses.InsertData2.verified.cs │ │ │ ├── Module#Timers.Init.verified.cs │ │ │ ├── Module#Timers.SendMessageTimer.verified.cs │ │ │ ├── Module#Timers.SendScheduledMessage.verified.cs │ │ │ ├── Type#ContainsNestedLists.verified.cs │ │ │ ├── Type#CustomClass.verified.cs │ │ │ ├── Type#CustomNestedClass.verified.cs │ │ │ ├── Type#CustomRecord.verified.cs │ │ │ ├── Type#CustomStruct.verified.cs │ │ │ ├── Type#CustomTaggedEnum.verified.cs │ │ │ ├── Type#EmptyClass.verified.cs │ │ │ ├── Type#EmptyRecord.verified.cs │ │ │ ├── Type#EmptyStruct.verified.cs │ │ │ └── Type#FormerlyForbiddenFieldNames.verified.cs │ │ ├── Directory.Build.props │ │ ├── README.md │ │ ├── Runtime/ │ │ │ ├── Attrs.cs │ │ │ ├── AuthCtx.cs │ │ │ ├── Exceptions.cs │ │ │ ├── Filter.cs │ │ │ ├── Http.cs │ │ │ ├── Internal/ │ │ │ │ ├── .gitattributes │ │ │ │ ├── Autogen/ │ │ │ │ │ ├── CaseConversionPolicy.g.cs │ │ │ │ │ ├── ExplicitNameEntry.g.cs │ │ │ │ │ ├── ExplicitNames.g.cs │ │ │ │ │ ├── FunctionVisibility.g.cs │ │ │ │ │ ├── IndexType.g.cs │ │ │ │ │ ├── Lifecycle.g.cs │ │ │ │ │ ├── MiscModuleExport.g.cs │ │ │ │ │ ├── NameMapping.g.cs │ │ │ │ │ ├── RawColumnDefV8.g.cs │ │ │ │ │ ├── RawColumnDefaultValueV10.g.cs │ │ │ │ │ ├── RawColumnDefaultValueV9.g.cs │ │ │ │ │ ├── RawConstraintDataV9.g.cs │ │ │ │ │ ├── RawConstraintDefV10.g.cs │ │ │ │ │ ├── RawConstraintDefV8.g.cs │ │ │ │ │ ├── RawConstraintDefV9.g.cs │ │ │ │ │ ├── RawIndexAlgorithm.g.cs │ │ │ │ │ ├── RawIndexDefV10.g.cs │ │ │ │ │ ├── RawIndexDefV8.g.cs │ │ │ │ │ ├── RawIndexDefV9.g.cs │ │ │ │ │ ├── RawLifeCycleReducerDefV10.g.cs │ │ │ │ │ ├── RawMiscModuleExportV9.g.cs │ │ │ │ │ ├── RawModuleDef.g.cs │ │ │ │ │ ├── RawModuleDefV10.g.cs │ │ │ │ │ ├── RawModuleDefV10Section.g.cs │ │ │ │ │ ├── RawModuleDefV8.g.cs │ │ │ │ │ ├── RawModuleDefV9.g.cs │ │ │ │ │ ├── RawProcedureDefV10.g.cs │ │ │ │ │ ├── RawProcedureDefV9.g.cs │ │ │ │ │ ├── RawReducerDefV10.g.cs │ │ │ │ │ ├── RawReducerDefV9.g.cs │ │ │ │ │ ├── RawRowLevelSecurityDefV9.g.cs │ │ │ │ │ ├── RawScheduleDefV10.g.cs │ │ │ │ │ ├── RawScheduleDefV9.g.cs │ │ │ │ │ ├── RawScopedTypeNameV10.g.cs │ │ │ │ │ ├── RawScopedTypeNameV9.g.cs │ │ │ │ │ ├── RawSequenceDefV10.g.cs │ │ │ │ │ ├── RawSequenceDefV8.g.cs │ │ │ │ │ ├── RawSequenceDefV9.g.cs │ │ │ │ │ ├── RawTableDefV10.g.cs │ │ │ │ │ ├── RawTableDefV8.g.cs │ │ │ │ │ ├── RawTableDefV9.g.cs │ │ │ │ │ ├── RawTypeDefV10.g.cs │ │ │ │ │ ├── RawTypeDefV9.g.cs │ │ │ │ │ ├── RawUniqueConstraintDataV9.g.cs │ │ │ │ │ ├── RawViewDefV10.g.cs │ │ │ │ │ ├── RawViewDefV9.g.cs │ │ │ │ │ ├── ReducerDef.g.cs │ │ │ │ │ ├── TableAccess.g.cs │ │ │ │ │ ├── TableDesc.g.cs │ │ │ │ │ ├── TableType.g.cs │ │ │ │ │ ├── TypeAlias.g.cs │ │ │ │ │ └── Typespace.g.cs │ │ │ │ ├── Bounds.cs │ │ │ │ ├── FFI.cs │ │ │ │ ├── IIndex.cs │ │ │ │ ├── IReducer.cs │ │ │ │ ├── ITable.cs │ │ │ │ ├── IView.cs │ │ │ │ ├── Module.cs │ │ │ │ ├── Procedure.cs │ │ │ │ ├── TxContext.cs │ │ │ │ └── ViewResultHeader.cs │ │ │ ├── JwtClaims.cs │ │ │ ├── Log.cs │ │ │ ├── LogStopwatch.cs │ │ │ ├── ProcedureContext.cs │ │ │ ├── README.md │ │ │ ├── Runtime.csproj │ │ │ ├── bindings.c │ │ │ ├── build/ │ │ │ │ ├── SpacetimeDB.Runtime.props │ │ │ │ └── SpacetimeDB.Runtime.targets │ │ │ └── driver.h │ │ ├── Runtime.Tests/ │ │ │ ├── JwtClaimsTest.cs │ │ │ └── Runtime.Tests.csproj │ │ └── SpacetimeSharpSATS.sln │ ├── bindings-macro/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ ├── procedure.rs │ │ ├── reducer.rs │ │ ├── sats.rs │ │ ├── table.rs │ │ ├── util.rs │ │ └── view.rs │ ├── bindings-sys/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── bindings-typescript/ │ │ ├── .editorconfig │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── DEVELOP.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── angular/ │ │ │ │ ├── connection_state.ts │ │ │ │ ├── index.ts │ │ │ │ ├── injectors/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── inject-reducer.ts │ │ │ │ │ ├── inject-spacetimedb-connected.ts │ │ │ │ │ ├── inject-spacetimedb.ts │ │ │ │ │ └── inject-table.ts │ │ │ │ └── providers/ │ │ │ │ ├── index.ts │ │ │ │ └── provide-spacetimedb.ts │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── algebraic_type.ts │ │ │ │ ├── algebraic_type_variants.ts │ │ │ │ ├── algebraic_value.ts │ │ │ │ ├── autogen/ │ │ │ │ │ └── types.ts │ │ │ │ ├── binary_reader.ts │ │ │ │ ├── binary_writer.ts │ │ │ │ ├── connection_id.ts │ │ │ │ ├── constraints.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── filter.ts │ │ │ │ ├── http_types.ts │ │ │ │ ├── identity.ts │ │ │ │ ├── indexes.ts │ │ │ │ ├── option.ts │ │ │ │ ├── query.ts │ │ │ │ ├── reducer_schema.ts │ │ │ │ ├── reducers.ts │ │ │ │ ├── result.ts │ │ │ │ ├── schedule_at.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── table.ts │ │ │ │ ├── table_schema.ts │ │ │ │ ├── time_duration.ts │ │ │ │ ├── timestamp.ts │ │ │ │ ├── type_builders.test-d.ts │ │ │ │ ├── type_builders.ts │ │ │ │ ├── type_util.ts │ │ │ │ ├── util.ts │ │ │ │ └── uuid.ts │ │ │ ├── react/ │ │ │ │ ├── SpacetimeDBProvider.ts │ │ │ │ ├── connection_state.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useReducer.ts │ │ │ │ ├── useSpacetimeDB.ts │ │ │ │ └── useTable.ts │ │ │ ├── sdk/ │ │ │ │ ├── client_api/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types/ │ │ │ │ │ │ ├── procedures.ts │ │ │ │ │ │ └── reducers.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── client_cache.ts │ │ │ │ ├── client_table.ts │ │ │ │ ├── connection_manager.ts │ │ │ │ ├── db_connection_builder.ts │ │ │ │ ├── db_connection_impl.ts │ │ │ │ ├── db_context.ts │ │ │ │ ├── db_view.ts │ │ │ │ ├── decompress.ts │ │ │ │ ├── event.ts │ │ │ │ ├── event_context.ts │ │ │ │ ├── event_emitter.ts │ │ │ │ ├── index.ts │ │ │ │ ├── internal.ts │ │ │ │ ├── json_api.ts │ │ │ │ ├── logger.ts │ │ │ │ ├── message_types.ts │ │ │ │ ├── procedures.ts │ │ │ │ ├── reducer_event.ts │ │ │ │ ├── reducer_handle.ts │ │ │ │ ├── reducers.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── spacetime_module.ts │ │ │ │ ├── subscription_builder_impl.ts │ │ │ │ ├── table_cache.ts │ │ │ │ ├── type_utils.ts │ │ │ │ ├── version.ts │ │ │ │ ├── websocket_decompress_adapter.ts │ │ │ │ ├── websocket_test_adapter.ts │ │ │ │ └── ws.ts │ │ │ ├── server/ │ │ │ │ ├── console.ts │ │ │ │ ├── db_view.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── http.ts │ │ │ │ ├── http_internal.ts │ │ │ │ ├── index.ts │ │ │ │ ├── polyfills.ts │ │ │ │ ├── procedures.ts │ │ │ │ ├── query.ts │ │ │ │ ├── range.ts │ │ │ │ ├── reducers.ts │ │ │ │ ├── rng.ts │ │ │ │ ├── runtime.ts │ │ │ │ ├── schema.test-d.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── sys.d.ts │ │ │ │ ├── view.test-d.ts │ │ │ │ └── views.ts │ │ │ ├── svelte/ │ │ │ │ ├── SpacetimeDBProvider.ts │ │ │ │ ├── connection_state.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useReducer.ts │ │ │ │ ├── useSpacetimeDB.ts │ │ │ │ └── useTable.ts │ │ │ ├── tanstack/ │ │ │ │ ├── SpacetimeDBQueryClient.ts │ │ │ │ ├── hooks.ts │ │ │ │ └── index.ts │ │ │ ├── util-stub.ts │ │ │ └── vue/ │ │ │ ├── SpacetimeDBProvider.ts │ │ │ ├── connection_state.ts │ │ │ ├── index.ts │ │ │ ├── useReducer.ts │ │ │ ├── useSpacetimeDB.ts │ │ │ └── useTable.ts │ │ ├── test-app/ │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── server/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── src/ │ │ │ │ ├── .gitattributes │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── index.css │ │ │ │ ├── main.tsx │ │ │ │ └── module_bindings/ │ │ │ │ ├── create_player_reducer.ts │ │ │ │ ├── index.ts │ │ │ │ ├── player_table.ts │ │ │ │ ├── types/ │ │ │ │ │ ├── procedures.ts │ │ │ │ │ └── reducers.ts │ │ │ │ ├── types.ts │ │ │ │ ├── unindexed_player_table.ts │ │ │ │ └── user_table.ts │ │ │ ├── tsconfig.app.json │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ ├── test-react-router-app/ │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── server/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── src/ │ │ │ │ ├── .gitattributes │ │ │ │ ├── App.css │ │ │ │ ├── App.tsx │ │ │ │ ├── index.css │ │ │ │ ├── main.tsx │ │ │ │ ├── module_bindings/ │ │ │ │ │ ├── clear_counter_reducer.ts │ │ │ │ │ ├── client_connected_reducer.ts │ │ │ │ │ ├── client_disconnected_reducer.ts │ │ │ │ │ ├── counter_table.ts │ │ │ │ │ ├── counter_type.ts │ │ │ │ │ ├── increment_counter_reducer.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── offline_user_table.ts │ │ │ │ │ ├── types/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── procedures.ts │ │ │ │ │ │ └── reducers.ts │ │ │ │ │ ├── user_table.ts │ │ │ │ │ └── user_type.ts │ │ │ │ └── pages/ │ │ │ │ ├── CounterPage.tsx │ │ │ │ └── UserPage.tsx │ │ │ ├── tsconfig.app.json │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ ├── tests/ │ │ │ ├── algebraic_type.test.ts │ │ │ ├── binary_read_write.test.ts │ │ │ ├── client_query.test.ts │ │ │ ├── column_metadata_validation.test.ts │ │ │ ├── connection_manager.test.ts │ │ │ ├── db_connection.test.ts │ │ │ ├── index.test.ts │ │ │ ├── logger.test.ts │ │ │ ├── query.test.ts │ │ │ ├── query_error_message.test.ts │ │ │ ├── schema_index_resolution.test.ts │ │ │ ├── serde.test.ts │ │ │ ├── table_cache.test.ts │ │ │ ├── table_cache_resolved_indexes.test.ts │ │ │ ├── table_index_accessor.test.ts │ │ │ ├── utils.ts │ │ │ ├── uuid.test.ts │ │ │ └── version.test.ts │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── tsconfig.typecheck.json │ │ ├── tsup.config.ts │ │ └── vitest.config.ts │ ├── cli/ │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── clippy.toml │ │ ├── src/ │ │ │ ├── api.rs │ │ │ ├── common_args.rs │ │ │ ├── config.rs │ │ │ ├── detect.rs │ │ │ ├── edit_distance.rs │ │ │ ├── errors.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ ├── spacetime_config.rs │ │ │ ├── subcommands/ │ │ │ │ ├── build.rs │ │ │ │ ├── call.rs │ │ │ │ ├── db_arg_resolution.rs │ │ │ │ ├── delete.rs │ │ │ │ ├── describe.rs │ │ │ │ ├── dev.rs │ │ │ │ ├── dns.rs │ │ │ │ ├── generate.rs │ │ │ │ ├── init.rs │ │ │ │ ├── list.rs │ │ │ │ ├── login.rs │ │ │ │ ├── logout.rs │ │ │ │ ├── logs.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── project/ │ │ │ │ │ └── typescript/ │ │ │ │ │ ├── _gitignore │ │ │ │ │ ├── index._ts │ │ │ │ │ ├── package._json │ │ │ │ │ └── tsconfig._json │ │ │ │ ├── publish.rs │ │ │ │ ├── repl.rs │ │ │ │ ├── server.rs │ │ │ │ ├── sql.rs │ │ │ │ ├── start.rs │ │ │ │ ├── subscribe.rs │ │ │ │ └── version.rs │ │ │ ├── tasks/ │ │ │ │ ├── cpp.rs │ │ │ │ ├── csharp.rs │ │ │ │ ├── javascript.rs │ │ │ │ ├── mod.rs │ │ │ │ └── rust.rs │ │ │ ├── util.rs │ │ │ └── version.rs │ │ └── tools/ │ │ └── sublime/ │ │ └── SpacetimeDBSQL.sublime-syntax │ ├── client-api/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── auth.rs │ │ ├── lib.rs │ │ ├── routes/ │ │ │ ├── database.rs │ │ │ ├── energy.rs │ │ │ ├── health.rs │ │ │ ├── identity.rs │ │ │ ├── internal.rs │ │ │ ├── metrics.rs │ │ │ ├── mod.rs │ │ │ ├── prometheus.rs │ │ │ └── subscribe.rs │ │ ├── util/ │ │ │ ├── flat_csv.rs │ │ │ ├── serde.rs │ │ │ └── websocket.rs │ │ └── util.rs │ ├── client-api-messages/ │ │ ├── Cargo.toml │ │ ├── DEVELOP.md │ │ ├── README.md │ │ ├── examples/ │ │ │ ├── get_ws_schema.rs │ │ │ └── get_ws_schema_v2.rs │ │ ├── src/ │ │ │ ├── energy.rs │ │ │ ├── http.rs │ │ │ ├── lib.rs │ │ │ ├── name/ │ │ │ │ └── tests.rs │ │ │ ├── name.rs │ │ │ ├── websocket/ │ │ │ │ ├── common.rs │ │ │ │ ├── v1.rs │ │ │ │ └── v2.rs │ │ │ └── websocket.rs │ │ └── ws_schema-2.json │ ├── codegen/ │ │ ├── Cargo.toml │ │ ├── examples/ │ │ │ ├── regen-cpp-moduledef.rs │ │ │ ├── regen-csharp-moduledef.rs │ │ │ └── regen-typescript-moduledef.rs │ │ ├── src/ │ │ │ ├── UnrealCPP-README.md │ │ │ ├── code_indenter.rs │ │ │ ├── cpp.rs │ │ │ ├── csharp.rs │ │ │ ├── lib.rs │ │ │ ├── rust.rs │ │ │ ├── typescript.rs │ │ │ ├── unrealcpp.rs │ │ │ └── util.rs │ │ └── tests/ │ │ ├── codegen.rs │ │ └── snapshots/ │ │ ├── codegen__codegen_csharp.snap │ │ ├── codegen__codegen_rust.snap │ │ └── codegen__codegen_typescript.snap │ ├── commitlog/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── proptest-regressions/ │ │ │ ├── commit.txt │ │ │ └── tests/ │ │ │ └── bitflip.txt │ │ ├── src/ │ │ │ ├── commit.rs │ │ │ ├── commitlog.rs │ │ │ ├── error.rs │ │ │ ├── index/ │ │ │ │ ├── indexfile.rs │ │ │ │ └── mod.rs │ │ │ ├── lib.rs │ │ │ ├── payload/ │ │ │ │ └── txdata.rs │ │ │ ├── payload.rs │ │ │ ├── repo/ │ │ │ │ ├── fs.rs │ │ │ │ ├── mem/ │ │ │ │ │ └── segment.rs │ │ │ │ ├── mem.rs │ │ │ │ └── mod.rs │ │ │ ├── segment.rs │ │ │ ├── stream/ │ │ │ │ ├── common.rs │ │ │ │ ├── reader.rs │ │ │ │ └── writer.rs │ │ │ ├── stream.rs │ │ │ ├── tests/ │ │ │ │ ├── bitflip.rs │ │ │ │ ├── helpers.rs │ │ │ │ └── partial.rs │ │ │ ├── tests.rs │ │ │ ├── varchar.rs │ │ │ └── varint.rs │ │ └── tests/ │ │ ├── io.rs │ │ ├── random_payload/ │ │ │ └── mod.rs │ │ └── streaming/ │ │ └── mod.rs │ ├── core/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── proptest-regressions/ │ │ │ ├── db/ │ │ │ │ └── datastore/ │ │ │ │ └── locking_tx_datastore/ │ │ │ │ └── delete_table.txt │ │ │ └── host/ │ │ │ └── v8/ │ │ │ ├── ser.txt │ │ │ └── to_value.txt │ │ ├── src/ │ │ │ ├── auth/ │ │ │ │ ├── mod.rs │ │ │ │ └── token_validation.rs │ │ │ ├── callgrind_flag.rs │ │ │ ├── client/ │ │ │ │ ├── client_connection.rs │ │ │ │ ├── client_connection_index.rs │ │ │ │ ├── consume_each_list.rs │ │ │ │ ├── message_handlers.rs │ │ │ │ ├── message_handlers_v1.rs │ │ │ │ ├── message_handlers_v2.rs │ │ │ │ └── messages.rs │ │ │ ├── client.rs │ │ │ ├── config.rs │ │ │ ├── database_logger.rs │ │ │ ├── db/ │ │ │ │ ├── durability.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── persistence.rs │ │ │ │ ├── relational_db.rs │ │ │ │ ├── snapshot.rs │ │ │ │ └── update.rs │ │ │ ├── energy.rs │ │ │ ├── error.rs │ │ │ ├── estimation.rs │ │ │ ├── host/ │ │ │ │ ├── disk_storage.rs │ │ │ │ ├── host_controller.rs │ │ │ │ ├── instance_env.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── module_common.rs │ │ │ │ ├── module_host.rs │ │ │ │ ├── scheduler.rs │ │ │ │ ├── v8/ │ │ │ │ │ ├── budget.rs │ │ │ │ │ ├── builtins/ │ │ │ │ │ │ ├── delete_math_random.js │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── text_encoding.js │ │ │ │ │ │ └── types.d.ts │ │ │ │ │ ├── de.rs │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── from_value.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── ser.rs │ │ │ │ │ ├── string.rs │ │ │ │ │ ├── syscall/ │ │ │ │ │ │ ├── common.rs │ │ │ │ │ │ ├── hooks.rs │ │ │ │ │ │ ├── mod.rs │ │ │ │ │ │ ├── v1.rs │ │ │ │ │ │ └── v2.rs │ │ │ │ │ ├── to_value.rs │ │ │ │ │ └── util.rs │ │ │ │ ├── wasm_common/ │ │ │ │ │ ├── abi.rs │ │ │ │ │ ├── instrumentation.rs │ │ │ │ │ └── module_host_actor.rs │ │ │ │ ├── wasm_common.rs │ │ │ │ └── wasmtime/ │ │ │ │ ├── mod.rs │ │ │ │ ├── pooling_stack_creator.rs │ │ │ │ ├── wasm_instance_env.rs │ │ │ │ └── wasmtime_module.rs │ │ │ ├── lib.rs │ │ │ ├── messages/ │ │ │ │ ├── control_db.rs │ │ │ │ ├── control_worker_api.rs │ │ │ │ ├── instance_db_trace_log.rs │ │ │ │ ├── mod.rs │ │ │ │ └── worker_db.rs │ │ │ ├── module_host_context.rs │ │ │ ├── replica_context.rs │ │ │ ├── sql/ │ │ │ │ ├── ast.rs │ │ │ │ ├── execute.rs │ │ │ │ ├── mod.rs │ │ │ │ └── parser.rs │ │ │ ├── startup.rs │ │ │ ├── subscription/ │ │ │ │ ├── delta.rs │ │ │ │ ├── execution_unit.rs │ │ │ │ ├── metrics.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── module_subscription_actor.rs │ │ │ │ ├── module_subscription_manager.rs │ │ │ │ ├── query.rs │ │ │ │ ├── row_list_builder_pool.rs │ │ │ │ ├── subscription.rs │ │ │ │ ├── tx.rs │ │ │ │ └── websocket_building.rs │ │ │ ├── util/ │ │ │ │ ├── jobs.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── notify_once.rs │ │ │ │ └── prometheus_handle.rs │ │ │ └── worker_metrics/ │ │ │ └── mod.rs │ │ └── testdata/ │ │ ├── README.md │ │ └── v1.2/ │ │ └── replicas/ │ │ └── 22000001/ │ │ ├── clog/ │ │ │ ├── 00000000000000000000.stdb.log │ │ │ └── 00000000000000000000.stdb.ofs │ │ ├── db.lock │ │ ├── module_logs/ │ │ │ └── 2025-08-18.log │ │ └── snapshots/ │ │ └── 00000000000000000000.snapshot_dir/ │ │ ├── 00000000000000000000.snapshot_bsatn │ │ └── objects/ │ │ ├── 19/ │ │ │ └── 30ce81246a4cdc25e9024ae0065d053adb2efbe1b5b7af457331d330e481e8 │ │ ├── 41/ │ │ │ └── bb11b6d2cdc488192ee70d8175307d6f205756ed163f4237c6cba2936798dc │ │ ├── 45/ │ │ │ └── 4d2e2c62ff5d46c5b3e6de72d6277eb285fc2d6b0a5ac6f92498e08a9e5ecc │ │ ├── 62/ │ │ │ └── 22df0e5ca93d3fb22762e12161246a1d5917c61ada5d81b8dcce12fd5780b3 │ │ ├── 79/ │ │ │ └── 4dced5633eca2ffee784d471f5203209169321083ef99de254ad24af0f6d5a │ │ ├── 95/ │ │ │ └── 74dd6d2857fa771a1cd16be31fdef38f83c2fd3bcc05f4934e53bdbfa21f10 │ │ └── 9a/ │ │ └── b95f5aaed7541289faa8bc4de886ce0281f11037c3424494e58fee92411241 │ ├── data-structures/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── error_stream.rs │ │ ├── lib.rs │ │ ├── map.rs │ │ ├── nstr.rs │ │ ├── object_pool.rs │ │ ├── slim_slice.rs │ │ └── small_map.rs │ ├── datastore/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── db_metrics/ │ │ │ ├── data_size.rs │ │ │ └── mod.rs │ │ ├── error.rs │ │ ├── execution_context.rs │ │ ├── lib.rs │ │ ├── locking_tx_datastore/ │ │ │ ├── committed_state.rs │ │ │ ├── datastore.rs │ │ │ ├── delete_table.rs │ │ │ ├── mod.rs │ │ │ ├── mut_tx.rs │ │ │ ├── sequence.rs │ │ │ ├── state_view.rs │ │ │ ├── tx.rs │ │ │ └── tx_state.rs │ │ ├── system_tables.rs │ │ └── traits.rs │ ├── durability/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── imp/ │ │ │ │ ├── local.rs │ │ │ │ ├── local.rs.orig │ │ │ │ └── mod.rs │ │ │ └── lib.rs │ │ └── tests/ │ │ ├── io/ │ │ │ ├── fallocate.rs │ │ │ └── mod.rs │ │ └── main.rs │ ├── execution/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── dml.rs │ │ ├── lib.rs │ │ └── pipelined.rs │ ├── expr/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── check.rs │ │ ├── errors.rs │ │ ├── expr.rs │ │ ├── lib.rs │ │ ├── rls.rs │ │ └── statement.rs │ ├── fs-utils/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── compression.rs │ │ ├── dir_trie.rs │ │ ├── lib.rs │ │ └── lockfile.rs │ ├── guard/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── lib/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── proptest-regressions/ │ │ │ ├── address.txt │ │ │ ├── db/ │ │ │ │ └── column_ordering.txt │ │ │ └── identity.txt │ │ ├── src/ │ │ │ ├── connection_id.rs │ │ │ ├── db/ │ │ │ │ ├── attr.rs │ │ │ │ ├── auth.rs │ │ │ │ ├── default_element_ordering.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── raw_def/ │ │ │ │ │ ├── v10.rs │ │ │ │ │ ├── v8.rs │ │ │ │ │ └── v9.rs │ │ │ │ ├── raw_def.rs │ │ │ │ └── view.rs │ │ │ ├── direct_index_key.rs │ │ │ ├── error.rs │ │ │ ├── filterable_value.rs │ │ │ ├── http.rs │ │ │ ├── identity.rs │ │ │ ├── lib.rs │ │ │ ├── metrics.rs │ │ │ ├── operator.rs │ │ │ ├── query.rs │ │ │ ├── scheduler.rs │ │ │ ├── st_var.rs │ │ │ └── version.rs │ │ └── tests/ │ │ ├── serde.rs │ │ └── snapshots/ │ │ ├── serde__json_mappings-2.snap │ │ └── serde__json_mappings.snap │ ├── memory-usage/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── metrics/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── lib.rs │ │ └── typed_prometheus.rs │ ├── paths/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── cli.rs │ │ ├── lib.rs │ │ ├── server.rs │ │ ├── standalone.rs │ │ └── utils.rs │ ├── pg/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── encoder.rs │ │ ├── lib.rs │ │ └── pg_server.rs │ ├── physical-plan/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── compile.rs │ │ ├── dml.rs │ │ ├── lib.rs │ │ ├── plan.rs │ │ └── rules.rs │ ├── primitives/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── proptest-regressions/ │ │ │ └── col_list.txt │ │ └── src/ │ │ ├── attr.rs │ │ ├── col_list.rs │ │ ├── errno.rs │ │ ├── ids.rs │ │ └── lib.rs │ ├── query/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── query-builder/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── expr.rs │ │ ├── join.rs │ │ ├── lib.rs │ │ └── table.rs │ ├── sats/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── proptest-regressions/ │ │ │ ├── algebraic_value_hash.txt │ │ │ ├── timestamp.txt │ │ │ └── typespace.txt │ │ ├── src/ │ │ │ ├── algebraic_type/ │ │ │ │ ├── fmt.rs │ │ │ │ └── map_notation.rs │ │ │ ├── algebraic_type.rs │ │ │ ├── algebraic_type_ref.rs │ │ │ ├── algebraic_value/ │ │ │ │ ├── de.rs │ │ │ │ └── ser.rs │ │ │ ├── algebraic_value.rs │ │ │ ├── algebraic_value_hash.rs │ │ │ ├── array_type.rs │ │ │ ├── array_value.rs │ │ │ ├── bsatn/ │ │ │ │ ├── de.rs │ │ │ │ ├── eq.rs │ │ │ │ └── ser.rs │ │ │ ├── bsatn.rs │ │ │ ├── buffer.rs │ │ │ ├── convert.rs │ │ │ ├── de/ │ │ │ │ ├── impls.rs │ │ │ │ └── serde.rs │ │ │ ├── de.rs │ │ │ ├── hash.rs │ │ │ ├── hex.rs │ │ │ ├── layout.rs │ │ │ ├── lib.rs │ │ │ ├── memory_usage_impls.rs │ │ │ ├── meta_type.rs │ │ │ ├── primitives.rs │ │ │ ├── product_type.rs │ │ │ ├── product_type_element.rs │ │ │ ├── product_value.rs │ │ │ ├── proptest.rs │ │ │ ├── raw_identifier.rs │ │ │ ├── resolve_refs.rs │ │ │ ├── satn.rs │ │ │ ├── ser/ │ │ │ │ ├── impls.rs │ │ │ │ └── serde.rs │ │ │ ├── ser.rs │ │ │ ├── size_of.rs │ │ │ ├── sum_type.rs │ │ │ ├── sum_type_variant.rs │ │ │ ├── sum_value.rs │ │ │ ├── time_duration.rs │ │ │ ├── timestamp.rs │ │ │ ├── typespace.rs │ │ │ └── uuid.rs │ │ └── tests/ │ │ └── encoding_roundtrip.proptest-regressions │ ├── schema/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── proptest-regressions/ │ │ │ └── type_for_generate.txt │ │ ├── src/ │ │ │ ├── auto_migrate/ │ │ │ │ ├── formatter.rs │ │ │ │ └── termcolor_formatter.rs │ │ │ ├── auto_migrate.rs │ │ │ ├── def/ │ │ │ │ ├── deserialize.rs │ │ │ │ ├── error.rs │ │ │ │ ├── validate/ │ │ │ │ │ ├── v10.rs │ │ │ │ │ ├── v8.rs │ │ │ │ │ └── v9.rs │ │ │ │ └── validate.rs │ │ │ ├── def.rs │ │ │ ├── error.rs │ │ │ ├── identifier.rs │ │ │ ├── lib.rs │ │ │ ├── reducer_name.rs │ │ │ ├── relation.rs │ │ │ ├── reserved_identifiers.txt │ │ │ ├── schema.rs │ │ │ ├── snapshots/ │ │ │ │ ├── spacetimedb_schema__auto_migrate__tests__empty_to_populated_migration.snap │ │ │ │ ├── spacetimedb_schema__auto_migrate__tests__updated pretty print no color.snap │ │ │ │ └── spacetimedb_schema__auto_migrate__tests__updated pretty print.snap │ │ │ ├── table_name.rs │ │ │ └── type_for_generate.rs │ │ └── tests/ │ │ └── ensure_same_schema.rs │ ├── smoketests/ │ │ ├── Cargo.toml │ │ ├── DEVELOP.md │ │ ├── fixtures/ │ │ │ ├── README.md │ │ │ └── upgrade_old_module_v1.wasm │ │ ├── modules/ │ │ │ ├── Cargo.toml │ │ │ ├── add-remove-index/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── add-remove-index-indexed/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── autoinc-basic/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── autoinc-unique/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── call-empty/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── call-reducer-procedure/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── client-connection-disconnect-panic/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── client-connection-reject/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── confirmed-reads/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── connect-disconnect/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── delete-database/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── describe/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── dml/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── fail-initial-publish-broken/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── fail-initial-publish-fixed/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── filtering/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── hotswap-basic/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── hotswap-updated/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── logs-level-filter/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── module-nested-op/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── modules-add-table/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── modules-basic/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── modules-breaking/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── namespaces/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── new-user-flow/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── panic/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── panic-error/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── permissions-lifecycle/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── permissions-private/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── pg-wire/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── restart-connected-client/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── restart-person/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── rls/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── rls-no-filter/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── rls-with-filter/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── schedule-cancel/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── schedule-procedure/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── schedule-subscribe/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── schedule-volatile/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── sql-format/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── upload-module-2/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-auto-migrate/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-auto-migrate-updated/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-basic/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-broken-namespace/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-broken-return-type/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-callable/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-count/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-drop-view/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-query/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-recovered/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-sql/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── views-subscribe/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── views-trapped/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── src/ │ │ │ ├── csharp.rs │ │ │ ├── lib.rs │ │ │ └── modules.rs │ │ └── tests/ │ │ ├── integration.rs │ │ └── smoketests/ │ │ ├── add_remove_index.rs │ │ ├── auto_inc.rs │ │ ├── auto_migration.rs │ │ ├── call.rs │ │ ├── change_host_type.rs │ │ ├── cli/ │ │ │ ├── auth.rs │ │ │ ├── dev.rs │ │ │ ├── generate.rs │ │ │ ├── mod.rs │ │ │ ├── publish.rs │ │ │ └── server.rs │ │ ├── client_connection_errors.rs │ │ ├── confirmed_reads.rs │ │ ├── connect_disconnect_from_cli.rs │ │ ├── create_project.rs │ │ ├── csharp_module.rs │ │ ├── default_module_clippy.rs │ │ ├── delete_database.rs │ │ ├── describe.rs │ │ ├── detect_wasm_bindgen.rs │ │ ├── dml.rs │ │ ├── domains.rs │ │ ├── fail_initial_publish.rs │ │ ├── filtering.rs │ │ ├── http_egress.rs │ │ ├── logs_level_filter.rs │ │ ├── mod.rs │ │ ├── module_nested_op.rs │ │ ├── modules.rs │ │ ├── namespaces.rs │ │ ├── new_user_flow.rs │ │ ├── panic.rs │ │ ├── permissions.rs │ │ ├── pg_wire.rs │ │ ├── publish_upgrade_prompt.rs │ │ ├── quickstart.rs │ │ ├── restart.rs │ │ ├── rls.rs │ │ ├── schedule_reducer.rs │ │ ├── servers.rs │ │ ├── sql.rs │ │ ├── templates.rs │ │ ├── timestamp_route.rs │ │ └── views.rs │ ├── snapshot/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ └── remote.rs │ │ └── tests/ │ │ └── remote.rs │ ├── sql-parser/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── ast/ │ │ │ ├── mod.rs │ │ │ ├── sql.rs │ │ │ └── sub.rs │ │ ├── lib.rs │ │ └── parser/ │ │ ├── errors.rs │ │ ├── mod.rs │ │ ├── recursion.rs │ │ ├── sql.rs │ │ └── sub.rs │ ├── sqltest/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build_standard.py │ │ ├── clippy.toml │ │ ├── override_with_output.sh │ │ ├── reformat.sh │ │ ├── run_all_sequential.sh │ │ ├── src/ │ │ │ ├── db.rs │ │ │ ├── main.rs │ │ │ ├── pg.rs │ │ │ ├── space.rs │ │ │ └── sqlite.rs │ │ ├── standards/ │ │ │ ├── 2016/ │ │ │ │ ├── E/ │ │ │ │ │ ├── E011-01.tests.yml │ │ │ │ │ ├── E011-02.tests.yml │ │ │ │ │ ├── E011-03.tests.yml │ │ │ │ │ ├── E011-04.tests.yml │ │ │ │ │ ├── E011-05.tests.yml │ │ │ │ │ ├── E011-06.tests.yml │ │ │ │ │ ├── E021-01.tests.yml │ │ │ │ │ ├── E021-02.tests.yml │ │ │ │ │ ├── E021-03.tests.yml │ │ │ │ │ ├── E021-04.tests.yml │ │ │ │ │ ├── E021-05.tests.yml │ │ │ │ │ ├── E021-06.tests.yml │ │ │ │ │ ├── E021-07.tests.yml │ │ │ │ │ ├── E021-08.tests.yml │ │ │ │ │ ├── E021-09.tests.yml │ │ │ │ │ ├── E021-10.tests.yml │ │ │ │ │ ├── E021-11.tests.yml │ │ │ │ │ ├── E021-12.tests.yml │ │ │ │ │ ├── E031-01.tests.yml │ │ │ │ │ ├── E031-02.tests.yml │ │ │ │ │ ├── E031-03.tests.yml │ │ │ │ │ ├── E051-01.tests.yml │ │ │ │ │ ├── E051-02.tests.yml │ │ │ │ │ ├── E051-04.tests.yml │ │ │ │ │ ├── E051-05.tests.yml │ │ │ │ │ ├── E051-06.tests.yml │ │ │ │ │ ├── E051-07.tests.yml │ │ │ │ │ ├── E051-08.tests.yml │ │ │ │ │ ├── E051-09.tests.yml │ │ │ │ │ ├── E051.tests.yml │ │ │ │ │ ├── E061-01.tests.yml │ │ │ │ │ ├── E061-02.tests.yml │ │ │ │ │ ├── E061-03.tests.yml │ │ │ │ │ ├── E061-04.tests.yml │ │ │ │ │ ├── E061-05.tests.yml │ │ │ │ │ ├── E061-06.tests.yml │ │ │ │ │ ├── E061-07.tests.yml │ │ │ │ │ ├── E061-08.tests.yml │ │ │ │ │ ├── E061-09.tests.yml │ │ │ │ │ ├── E061-11.tests.yml │ │ │ │ │ ├── E061-12.tests.yml │ │ │ │ │ ├── E061-13.tests.yml │ │ │ │ │ ├── E061-14.tests.yml │ │ │ │ │ ├── E071-01.tests.yml │ │ │ │ │ ├── E071-02.tests.yml │ │ │ │ │ ├── E071-03.tests.yml │ │ │ │ │ ├── E071-05.tests.yml │ │ │ │ │ ├── E071-06.tests.yml │ │ │ │ │ ├── E081-01.tests.yml │ │ │ │ │ ├── E081-02.tests.yml │ │ │ │ │ ├── E081-03.tests.yml │ │ │ │ │ ├── E081-04.tests.yml │ │ │ │ │ ├── E081-05.tests.yml │ │ │ │ │ ├── E081-06.tests.yml │ │ │ │ │ ├── E081-07.tests.yml │ │ │ │ │ ├── E081-08.tests.yml │ │ │ │ │ ├── E081-09.tests.yml │ │ │ │ │ ├── E081-10.tests.yml │ │ │ │ │ ├── E091-01.tests.yml │ │ │ │ │ ├── E091-02.tests.yml │ │ │ │ │ ├── E091-03.tests.yml │ │ │ │ │ ├── E091-04.tests.yml │ │ │ │ │ ├── E091-05.tests.yml │ │ │ │ │ ├── E091-06.tests.yml │ │ │ │ │ ├── E091-07.tests.yml │ │ │ │ │ ├── E101-01.tests.yml │ │ │ │ │ ├── E101-03.tests.yml │ │ │ │ │ ├── E101-04.tests.yml │ │ │ │ │ ├── E111.tests.yml │ │ │ │ │ ├── E121-01.tests.yml │ │ │ │ │ ├── E121-02.tests.yml │ │ │ │ │ ├── E121-03.tests.yml │ │ │ │ │ ├── E121-04.tests.yml │ │ │ │ │ ├── E121-06.tests.yml │ │ │ │ │ ├── E121-07.tests.yml │ │ │ │ │ ├── E121-08.tests.yml │ │ │ │ │ ├── E121-10.tests.yml │ │ │ │ │ ├── E121-17.tests.yml │ │ │ │ │ ├── E131.tests.yml │ │ │ │ │ ├── E141-01.tests.yml │ │ │ │ │ ├── E141-02.tests.yml │ │ │ │ │ ├── E141-03.tests.yml │ │ │ │ │ ├── E141-04.tests.yml │ │ │ │ │ ├── E141-06.tests.yml │ │ │ │ │ ├── E141-07.tests.yml │ │ │ │ │ ├── E141-08.tests.yml │ │ │ │ │ ├── E141-10.tests.yml │ │ │ │ │ ├── E151-01.tests.yml │ │ │ │ │ ├── E151-02.tests.yml │ │ │ │ │ ├── E152-01.tests.yml │ │ │ │ │ ├── E152-02.tests.yml │ │ │ │ │ ├── E153.tests.yml │ │ │ │ │ └── E161.tests.yml │ │ │ │ ├── F/ │ │ │ │ │ ├── F031-01.tests.yml │ │ │ │ │ ├── F031-02.tests.yml │ │ │ │ │ ├── F031-03.tests.yml │ │ │ │ │ ├── F031-04.tests.yml │ │ │ │ │ ├── F031-13.tests.yml │ │ │ │ │ ├── F031-16.tests.yml │ │ │ │ │ ├── F031-19.tests.yml │ │ │ │ │ ├── F041-01.tests.yml │ │ │ │ │ ├── F041-02.tests.yml │ │ │ │ │ ├── F041-03.tests.yml │ │ │ │ │ ├── F041-04.tests.yml │ │ │ │ │ ├── F041-05.tests.yml │ │ │ │ │ ├── F041-07.tests.yml │ │ │ │ │ ├── F041-08.tests.yml │ │ │ │ │ ├── F051-01.tests.yml │ │ │ │ │ ├── F051-02.tests.yml │ │ │ │ │ ├── F051-03.tests.yml │ │ │ │ │ ├── F051-04.tests.yml │ │ │ │ │ ├── F051-05.tests.yml │ │ │ │ │ ├── F051-06.tests.yml │ │ │ │ │ ├── F051-07.tests.yml │ │ │ │ │ ├── F051-08.tests.yml │ │ │ │ │ ├── F081.tests.yml │ │ │ │ │ ├── F131-01.tests.yml │ │ │ │ │ ├── F131-02.tests.yml │ │ │ │ │ ├── F131-03.tests.yml │ │ │ │ │ ├── F131-04.tests.yml │ │ │ │ │ ├── F221.tests.yml │ │ │ │ │ ├── F261-01.tests.yml │ │ │ │ │ ├── F261-02.tests.yml │ │ │ │ │ ├── F261-03.tests.yml │ │ │ │ │ ├── F261-04.tests.yml │ │ │ │ │ ├── F311-01.tests.yml │ │ │ │ │ ├── F311-02.tests.yml │ │ │ │ │ ├── F311-03.tests.yml │ │ │ │ │ ├── F311-04.tests.yml │ │ │ │ │ ├── F311-05.tests.yml │ │ │ │ │ ├── F471.tests.yml │ │ │ │ │ └── F481.tests.yml │ │ │ │ ├── S/ │ │ │ │ │ └── S011.tests.yml │ │ │ │ ├── T/ │ │ │ │ │ ├── T321.tests.yml │ │ │ │ │ └── T631.tests.yml │ │ │ │ └── features.yml │ │ │ ├── LICENSE │ │ │ └── README.md │ │ └── test/ │ │ ├── basic/ │ │ │ ├── delete.slt │ │ │ ├── insert.slt │ │ │ ├── joins.slt │ │ │ ├── select.slt │ │ │ ├── test_data.slt │ │ │ ├── test_data_join.slt │ │ │ └── where.slt │ │ ├── sql_2016/ │ │ │ ├── E011_01.slt │ │ │ ├── E011_02.slt │ │ │ ├── E011_03.slt │ │ │ ├── E011_04.slt │ │ │ ├── E011_05.slt │ │ │ ├── E011_06.slt │ │ │ ├── E021_01.slt │ │ │ ├── E021_02.slt │ │ │ ├── E021_03.slt │ │ │ ├── E021_04.slt │ │ │ ├── E021_05.slt │ │ │ ├── E021_06.slt │ │ │ ├── E021_07.slt │ │ │ ├── E021_08.slt │ │ │ ├── E021_09.slt │ │ │ ├── E021_10.slt │ │ │ ├── E021_11.slt │ │ │ ├── E021_12.slt │ │ │ ├── E031_01.slt │ │ │ ├── E031_02.slt │ │ │ ├── E031_03.slt │ │ │ ├── E051.slt │ │ │ ├── E051_01.slt │ │ │ ├── E051_02.slt │ │ │ ├── E051_04.slt │ │ │ ├── E051_05.slt │ │ │ ├── E051_06.slt │ │ │ ├── E051_07.slt │ │ │ ├── E051_08.slt │ │ │ ├── E051_09.slt │ │ │ ├── E061_01.slt │ │ │ ├── E061_02.slt │ │ │ ├── E061_03.slt │ │ │ ├── E061_04.slt │ │ │ ├── E061_05.slt │ │ │ ├── E061_06.slt │ │ │ ├── E061_07.slt │ │ │ ├── E061_08.slt │ │ │ ├── E061_09.slt │ │ │ ├── E061_11.slt │ │ │ ├── E061_12.slt │ │ │ ├── E061_13.slt │ │ │ ├── E061_14.slt │ │ │ ├── E071_01.slt │ │ │ ├── E071_02.slt │ │ │ ├── E071_03.slt │ │ │ ├── E071_05.slt │ │ │ ├── E071_06.slt │ │ │ ├── E081_01.slt │ │ │ ├── E081_02.slt │ │ │ ├── E081_03.slt │ │ │ ├── E081_04.slt │ │ │ ├── E081_05.slt │ │ │ ├── E081_06.slt │ │ │ ├── E081_07.slt │ │ │ ├── E081_08.slt │ │ │ ├── E081_09.slt │ │ │ ├── E081_10.slt │ │ │ ├── E091_01.slt │ │ │ ├── E091_02.slt │ │ │ ├── E091_03.slt │ │ │ ├── E091_04.slt │ │ │ ├── E091_05.slt │ │ │ ├── E091_06.slt │ │ │ ├── E091_07.slt │ │ │ ├── E101_01.slt │ │ │ ├── E101_03.slt │ │ │ ├── E101_04.slt │ │ │ ├── E111.slt │ │ │ ├── E121_01.slt │ │ │ ├── E121_02.slt │ │ │ ├── E121_03.slt │ │ │ ├── E121_04.slt │ │ │ ├── E121_06.slt │ │ │ ├── E121_07.slt │ │ │ ├── E121_08.slt │ │ │ ├── E121_10.slt │ │ │ ├── E121_17.slt │ │ │ ├── E131.slt │ │ │ ├── E141_01.slt │ │ │ ├── E141_02.slt │ │ │ ├── E141_03.slt │ │ │ ├── E141_04.slt │ │ │ ├── E141_06.slt │ │ │ ├── E141_07.slt │ │ │ ├── E141_08.slt │ │ │ ├── E141_10.slt │ │ │ ├── E151_01.slt │ │ │ ├── E151_02.slt │ │ │ ├── E152_01.slt │ │ │ ├── E152_02.slt │ │ │ ├── E153.slt │ │ │ ├── E161.slt │ │ │ ├── F031_01.slt │ │ │ ├── F031_02.slt │ │ │ ├── F031_03.slt │ │ │ ├── F031_04.slt │ │ │ ├── F031_13.slt │ │ │ ├── F031_16.slt │ │ │ ├── F031_19.slt │ │ │ ├── F041_01.slt │ │ │ ├── F041_02.slt │ │ │ ├── F041_03.slt │ │ │ ├── F041_04.slt │ │ │ ├── F041_05.slt │ │ │ ├── F041_07.slt │ │ │ ├── F041_08.slt │ │ │ ├── F051_01.slt │ │ │ ├── F051_02.slt │ │ │ ├── F051_03.slt │ │ │ ├── F051_04.slt │ │ │ ├── F051_05.slt │ │ │ ├── F051_06.slt │ │ │ ├── F051_07.slt │ │ │ ├── F051_08.slt │ │ │ ├── F081.slt │ │ │ ├── F131_01.slt │ │ │ ├── F131_02.slt │ │ │ ├── F131_03.slt │ │ │ ├── F131_04.slt │ │ │ ├── F221.slt │ │ │ ├── F261_01.slt │ │ │ ├── F261_02.slt │ │ │ ├── F261_03.slt │ │ │ ├── F261_04.slt │ │ │ ├── F311_01.slt │ │ │ ├── F311_02.slt │ │ │ ├── F311_03.slt │ │ │ ├── F311_04.slt │ │ │ ├── F311_05.slt │ │ │ ├── F471.slt │ │ │ ├── F481.slt │ │ │ ├── S011.slt │ │ │ └── T631.slt │ │ └── tutorial.slt │ ├── standalone/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── config.toml │ │ └── src/ │ │ ├── control_db/ │ │ │ └── tests.rs │ │ ├── control_db.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── subcommands/ │ │ │ ├── extract_schema.rs │ │ │ ├── mod.rs │ │ │ └── start.rs │ │ ├── util.rs │ │ └── version.rs │ ├── subscription/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── table/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── benches/ │ │ │ ├── page.rs │ │ │ ├── page_manager.rs │ │ │ ├── pointer_map.rs │ │ │ └── var_len_visitor.rs │ │ ├── proptest-regressions/ │ │ │ ├── bflatn_to.txt │ │ │ ├── btree_index.txt │ │ │ ├── pointer_map.txt │ │ │ ├── read_column.txt │ │ │ ├── row_hash.txt │ │ │ ├── ser.txt │ │ │ ├── static_bsatn_validator.txt │ │ │ ├── table.txt │ │ │ └── table_index/ │ │ │ ├── mod.txt │ │ │ └── unique_directer_index.txt │ │ └── src/ │ │ ├── bflatn_from.rs │ │ ├── bflatn_to.rs │ │ ├── blob_store.rs │ │ ├── eq.rs │ │ ├── eq_to_pv.rs │ │ ├── fixed_bit_set.rs │ │ ├── indexes.rs │ │ ├── lib.rs │ │ ├── page.rs │ │ ├── page_pool.rs │ │ ├── pages.rs │ │ ├── pointer_map.rs │ │ ├── read_column.rs │ │ ├── row_hash.rs │ │ ├── row_type_visitor.rs │ │ ├── static_bsatn_validator.rs │ │ ├── static_layout.rs │ │ ├── table.rs │ │ ├── table_index/ │ │ │ ├── hash_index.rs │ │ │ ├── index.rs │ │ │ ├── key_size.rs │ │ │ ├── mod.rs │ │ │ ├── multimap.rs │ │ │ ├── same_key_entry.rs │ │ │ ├── unique_direct_fixed_cap_index.rs │ │ │ ├── unique_direct_index.rs │ │ │ ├── unique_hash_index.rs │ │ │ └── uniquemap.rs │ │ ├── util.rs │ │ └── var_len.rs │ ├── testing/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── src/ │ │ │ ├── lib.rs │ │ │ ├── modules.rs │ │ │ └── sdk.rs │ │ └── tests/ │ │ └── standalone_integration_test.rs │ └── update/ │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── spacetime-install.ps1 │ ├── spacetime-install.sh │ └── src/ │ ├── cli/ │ │ ├── install.rs │ │ ├── link.rs │ │ ├── list.rs │ │ ├── self_install.rs │ │ ├── uninstall.rs │ │ ├── upgrade.rs │ │ └── use.rs │ ├── cli.rs │ ├── main.rs │ ├── proxy.rs │ └── update_notice.rs ├── d3-flamegraph-base.html ├── demo/ │ └── Blackholio/ │ ├── .github/ │ │ └── workflows/ │ │ └── repo-migration-notice.yml │ ├── .gitignore │ ├── DEVELOP.md │ ├── README.md │ ├── client-unity/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── Assets/ │ │ │ ├── ArenaBorderMaterial.mat │ │ │ ├── ArenaBorderMaterial.mat.meta │ │ │ ├── CircleMaterial.mat │ │ │ ├── CircleMaterial.mat.meta │ │ │ ├── CirclePrefab.prefab │ │ │ ├── CirclePrefab.prefab.meta │ │ │ ├── CircleShader.shadergraph │ │ │ ├── CircleShader.shadergraph.meta │ │ │ ├── ColorConvert.cginc │ │ │ ├── ColorConvert.cginc.meta │ │ │ ├── FoodMaterial.mat │ │ │ ├── FoodMaterial.mat.meta │ │ │ ├── FoodPrefab.prefab │ │ │ ├── FoodPrefab.prefab.meta │ │ │ ├── LeaderboardRow.prefab │ │ │ ├── LeaderboardRow.prefab.meta │ │ │ ├── PlayModeTests/ │ │ │ │ ├── PlayModeExampleTest.cs │ │ │ │ ├── PlayModeExampleTest.cs.meta │ │ │ │ ├── PlayModeTests.asmdef │ │ │ │ └── PlayModeTests.asmdef.meta │ │ │ ├── PlayModeTests.meta │ │ │ ├── PlayerPrefab.prefab │ │ │ ├── PlayerPrefab.prefab.meta │ │ │ ├── RepeatingBackground.png.meta │ │ │ ├── RepeatingBackground.prefab │ │ │ ├── RepeatingBackground.prefab.meta │ │ │ ├── Scenes/ │ │ │ │ ├── Main.unity │ │ │ │ └── Main.unity.meta │ │ │ ├── Scenes.meta │ │ │ ├── Scripts/ │ │ │ │ ├── BugFixKludge.cs │ │ │ │ ├── BugFixKludge.cs.meta │ │ │ │ ├── CameraController.cs │ │ │ │ ├── CameraController.cs.meta │ │ │ │ ├── CircleController.cs │ │ │ │ ├── CircleController.cs.meta │ │ │ │ ├── DeathScreen.cs │ │ │ │ ├── DeathScreen.cs.meta │ │ │ │ ├── EntityController.cs │ │ │ │ ├── EntityController.cs.meta │ │ │ │ ├── Extensions.cs │ │ │ │ ├── Extensions.cs.meta │ │ │ │ ├── FoodController.cs │ │ │ │ ├── FoodController.cs.meta │ │ │ │ ├── GameManager.cs │ │ │ │ ├── GameManager.cs.meta │ │ │ │ ├── LeaderboardController.cs │ │ │ │ ├── LeaderboardController.cs.meta │ │ │ │ ├── LeaderboardRow.cs │ │ │ │ ├── LeaderboardRow.cs.meta │ │ │ │ ├── ParallaxBackground.cs │ │ │ │ ├── ParallaxBackground.cs.meta │ │ │ │ ├── PlayerController.cs │ │ │ │ ├── PlayerController.cs.meta │ │ │ │ ├── PrefabManager.cs │ │ │ │ ├── PrefabManager.cs.meta │ │ │ │ ├── SpacetimeDBCircleGame.asmdef │ │ │ │ ├── SpacetimeDBCircleGame.asmdef.meta │ │ │ │ ├── UIUsernameChooser.cs │ │ │ │ ├── UIUsernameChooser.cs.meta │ │ │ │ ├── autogen/ │ │ │ │ │ ├── Reducers/ │ │ │ │ │ │ ├── EnterGame.g.cs │ │ │ │ │ │ ├── EnterGame.g.cs.meta │ │ │ │ │ │ ├── PlayerSplit.g.cs │ │ │ │ │ │ ├── PlayerSplit.g.cs.meta │ │ │ │ │ │ ├── Respawn.g.cs │ │ │ │ │ │ ├── Respawn.g.cs.meta │ │ │ │ │ │ ├── Suicide.g.cs │ │ │ │ │ │ ├── Suicide.g.cs.meta │ │ │ │ │ │ ├── UpdatePlayerInput.g.cs │ │ │ │ │ │ └── UpdatePlayerInput.g.cs.meta │ │ │ │ │ ├── Reducers.meta │ │ │ │ │ ├── SpacetimeDBClient.g.cs │ │ │ │ │ ├── SpacetimeDBClient.g.cs.meta │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ ├── Circle.g.cs │ │ │ │ │ │ ├── Circle.g.cs.meta │ │ │ │ │ │ ├── Config.g.cs │ │ │ │ │ │ ├── Config.g.cs.meta │ │ │ │ │ │ ├── ConsumeEntityEvent.g.cs │ │ │ │ │ │ ├── ConsumeEntityEvent.g.cs.meta │ │ │ │ │ │ ├── Entity.g.cs │ │ │ │ │ │ ├── Entity.g.cs.meta │ │ │ │ │ │ ├── Food.g.cs │ │ │ │ │ │ ├── Food.g.cs.meta │ │ │ │ │ │ ├── Player.g.cs │ │ │ │ │ │ └── Player.g.cs.meta │ │ │ │ │ ├── Tables.meta │ │ │ │ │ ├── Types/ │ │ │ │ │ │ ├── Circle.g.cs │ │ │ │ │ │ ├── Circle.g.cs.meta │ │ │ │ │ │ ├── CircleDecayTimer.g.cs │ │ │ │ │ │ ├── CircleDecayTimer.g.cs.meta │ │ │ │ │ │ ├── CircleRecombineTimer.g.cs │ │ │ │ │ │ ├── CircleRecombineTimer.g.cs.meta │ │ │ │ │ │ ├── Config.g.cs │ │ │ │ │ │ ├── Config.g.cs.meta │ │ │ │ │ │ ├── ConsumeEntityEvent.g.cs │ │ │ │ │ │ ├── ConsumeEntityEvent.g.cs.meta │ │ │ │ │ │ ├── ConsumeEntityTimer.g.cs │ │ │ │ │ │ ├── ConsumeEntityTimer.g.cs.meta │ │ │ │ │ │ ├── DbVector2.g.cs │ │ │ │ │ │ ├── DbVector2.g.cs.meta │ │ │ │ │ │ ├── Entity.g.cs │ │ │ │ │ │ ├── Entity.g.cs.meta │ │ │ │ │ │ ├── Food.g.cs │ │ │ │ │ │ ├── Food.g.cs.meta │ │ │ │ │ │ ├── MoveAllPlayersTimer.g.cs │ │ │ │ │ │ ├── MoveAllPlayersTimer.g.cs.meta │ │ │ │ │ │ ├── Player.g.cs │ │ │ │ │ │ ├── Player.g.cs.meta │ │ │ │ │ │ ├── SpawnFoodTimer.g.cs │ │ │ │ │ │ └── SpawnFoodTimer.g.cs.meta │ │ │ │ │ └── Types.meta │ │ │ │ └── autogen.meta │ │ │ ├── Scripts.meta │ │ │ ├── Settings/ │ │ │ │ ├── Lit2DSceneTemplate.scenetemplate │ │ │ │ ├── Lit2DSceneTemplate.scenetemplate.meta │ │ │ │ ├── Renderer2D.asset │ │ │ │ ├── Renderer2D.asset.meta │ │ │ │ ├── Scenes/ │ │ │ │ │ ├── URP2DSceneTemplate.unity │ │ │ │ │ └── URP2DSceneTemplate.unity.meta │ │ │ │ ├── Scenes.meta │ │ │ │ ├── UniversalRP.asset │ │ │ │ └── UniversalRP.asset.meta │ │ │ ├── Settings.meta │ │ │ ├── StarBackground.png.meta │ │ │ ├── StarBackground.prefab │ │ │ ├── StarBackground.prefab.meta │ │ │ ├── TextMesh Pro/ │ │ │ │ ├── Documentation/ │ │ │ │ │ └── TextMesh Pro User Guide 2016.pdf.meta │ │ │ │ ├── Documentation.meta │ │ │ │ ├── Fonts/ │ │ │ │ │ ├── LiberationSans - OFL.txt │ │ │ │ │ ├── LiberationSans - OFL.txt.meta │ │ │ │ │ └── LiberationSans.ttf.meta │ │ │ │ ├── Fonts.meta │ │ │ │ ├── Resources/ │ │ │ │ │ ├── Fonts & Materials/ │ │ │ │ │ │ ├── LiberationSans SDF - Drop Shadow.mat │ │ │ │ │ │ ├── LiberationSans SDF - Drop Shadow.mat.meta │ │ │ │ │ │ ├── LiberationSans SDF - Fallback.asset │ │ │ │ │ │ ├── LiberationSans SDF - Fallback.asset.meta │ │ │ │ │ │ ├── LiberationSans SDF - Outline.mat │ │ │ │ │ │ ├── LiberationSans SDF - Outline.mat.meta │ │ │ │ │ │ ├── LiberationSans SDF.asset │ │ │ │ │ │ └── LiberationSans SDF.asset.meta │ │ │ │ │ ├── Fonts & Materials.meta │ │ │ │ │ ├── LineBreaking Following Characters.txt │ │ │ │ │ ├── LineBreaking Following Characters.txt.meta │ │ │ │ │ ├── LineBreaking Leading Characters.txt │ │ │ │ │ ├── LineBreaking Leading Characters.txt.meta │ │ │ │ │ ├── Sprite Assets/ │ │ │ │ │ │ ├── EmojiOne.asset │ │ │ │ │ │ └── EmojiOne.asset.meta │ │ │ │ │ ├── Sprite Assets.meta │ │ │ │ │ ├── Style Sheets/ │ │ │ │ │ │ ├── Default Style Sheet.asset │ │ │ │ │ │ └── Default Style Sheet.asset.meta │ │ │ │ │ ├── Style Sheets.meta │ │ │ │ │ ├── TMP Settings.asset │ │ │ │ │ └── TMP Settings.asset.meta │ │ │ │ ├── Resources.meta │ │ │ │ ├── Shaders/ │ │ │ │ │ ├── TMP_Bitmap-Custom-Atlas.shader │ │ │ │ │ ├── TMP_Bitmap-Custom-Atlas.shader.meta │ │ │ │ │ ├── TMP_Bitmap-Mobile.shader │ │ │ │ │ ├── TMP_Bitmap-Mobile.shader.meta │ │ │ │ │ ├── TMP_Bitmap.shader │ │ │ │ │ ├── TMP_Bitmap.shader.meta │ │ │ │ │ ├── TMP_SDF Overlay.shader │ │ │ │ │ ├── TMP_SDF Overlay.shader.meta │ │ │ │ │ ├── TMP_SDF SSD.shader │ │ │ │ │ ├── TMP_SDF SSD.shader.meta │ │ │ │ │ ├── TMP_SDF-Mobile Masking.shader │ │ │ │ │ ├── TMP_SDF-Mobile Masking.shader.meta │ │ │ │ │ ├── TMP_SDF-Mobile Overlay.shader │ │ │ │ │ ├── TMP_SDF-Mobile Overlay.shader.meta │ │ │ │ │ ├── TMP_SDF-Mobile SSD.shader │ │ │ │ │ ├── TMP_SDF-Mobile SSD.shader.meta │ │ │ │ │ ├── TMP_SDF-Mobile.shader │ │ │ │ │ ├── TMP_SDF-Mobile.shader.meta │ │ │ │ │ ├── TMP_SDF-Surface-Mobile.shader │ │ │ │ │ ├── TMP_SDF-Surface-Mobile.shader.meta │ │ │ │ │ ├── TMP_SDF-Surface.shader │ │ │ │ │ ├── TMP_SDF-Surface.shader.meta │ │ │ │ │ ├── TMP_SDF.shader │ │ │ │ │ ├── TMP_SDF.shader.meta │ │ │ │ │ ├── TMP_Sprite.shader │ │ │ │ │ ├── TMP_Sprite.shader.meta │ │ │ │ │ ├── TMPro.cginc │ │ │ │ │ ├── TMPro.cginc.meta │ │ │ │ │ ├── TMPro_Mobile.cginc │ │ │ │ │ ├── TMPro_Mobile.cginc.meta │ │ │ │ │ ├── TMPro_Properties.cginc │ │ │ │ │ ├── TMPro_Properties.cginc.meta │ │ │ │ │ ├── TMPro_Surface.cginc │ │ │ │ │ └── TMPro_Surface.cginc.meta │ │ │ │ ├── Shaders.meta │ │ │ │ ├── Sprites/ │ │ │ │ │ ├── EmojiOne Attribution.txt │ │ │ │ │ ├── EmojiOne Attribution.txt.meta │ │ │ │ │ ├── EmojiOne.json │ │ │ │ │ ├── EmojiOne.json.meta │ │ │ │ │ └── EmojiOne.png.meta │ │ │ │ └── Sprites.meta │ │ │ ├── TextMesh Pro.meta │ │ │ ├── UIUsernamePanel.png.meta │ │ │ ├── UniversalRenderPipelineGlobalSettings.asset │ │ │ ├── UniversalRenderPipelineGlobalSettings.asset.meta │ │ │ ├── WavyOutline.shadersubgraph │ │ │ └── WavyOutline.shadersubgraph.meta │ │ ├── Packages/ │ │ │ ├── manifest.json │ │ │ └── packages-lock.json │ │ └── ProjectSettings/ │ │ ├── AudioManager.asset │ │ ├── BurstAotSettings_StandaloneWindows.json │ │ ├── ClusterInputManager.asset │ │ ├── CommonBurstAotSettings.json │ │ ├── DynamicsManager.asset │ │ ├── EditorBuildSettings.asset │ │ ├── EditorSettings.asset │ │ ├── GraphicsSettings.asset │ │ ├── InputManager.asset │ │ ├── MemorySettings.asset │ │ ├── NavMeshAreas.asset │ │ ├── NetworkManager.asset │ │ ├── PackageManagerSettings.asset │ │ ├── Packages/ │ │ │ └── com.unity.testtools.codecoverage/ │ │ │ └── Settings.json │ │ ├── Physics2DSettings.asset │ │ ├── PresetManager.asset │ │ ├── ProjectSettings.asset │ │ ├── ProjectVersion.txt │ │ ├── QualitySettings.asset │ │ ├── SceneTemplateSettings.json │ │ ├── ShaderGraphSettings.asset │ │ ├── TagManager.asset │ │ ├── TimeManager.asset │ │ ├── URPProjectSettings.asset │ │ ├── UnityConnectSettings.asset │ │ ├── VFXManager.asset │ │ ├── VersionControlSettings.asset │ │ └── XRSettings.asset │ ├── client-unreal/ │ │ ├── Config/ │ │ │ ├── DefaultEditor.ini │ │ │ ├── DefaultEngine.ini │ │ │ ├── DefaultGame.ini │ │ │ └── DefaultInput.ini │ │ ├── Content/ │ │ │ ├── BP_BlackholioGameMode.uasset │ │ │ ├── BP_Circle.uasset │ │ │ ├── BP_Food.uasset │ │ │ ├── BP_GameManager.uasset │ │ │ ├── BP_PlayerController.uasset │ │ │ ├── BP_PlayerPawn.uasset │ │ │ ├── Blackholio.umap │ │ │ ├── Circle.uasset │ │ │ ├── Circle_Sprite.uasset │ │ │ ├── Gameplay/ │ │ │ │ ├── BP_ParallaxBackground.uasset │ │ │ │ ├── StarBackground.uasset │ │ │ │ ├── StarBackground_Sprite.uasset │ │ │ │ ├── WBP_Leaderboard.uasset │ │ │ │ ├── WBP_LeaderboardRow.uasset │ │ │ │ ├── WBP_Respawn.uasset │ │ │ │ └── WBP_UsernameChooser.uasset │ │ │ ├── Input/ │ │ │ │ ├── IA_InputLock.uasset │ │ │ │ ├── IA_Split.uasset │ │ │ │ ├── IA_Suicide.uasset │ │ │ │ └── IMC_Main.uasset │ │ │ ├── MFI_WavyOutline_Inst.uasset │ │ │ ├── MF_WavyOutline.uasset │ │ │ ├── MI_Circle.uasset │ │ │ ├── MI_Food.uasset │ │ │ ├── M_Circle.uasset │ │ │ └── WBP_Nameplate.uasset │ │ ├── Source/ │ │ │ ├── client_unreal/ │ │ │ │ ├── Private/ │ │ │ │ │ ├── BlackholioGameMode.cpp │ │ │ │ │ ├── BlackholioPlayerController.cpp │ │ │ │ │ ├── Circle.cpp │ │ │ │ │ ├── Entity.cpp │ │ │ │ │ ├── Food.cpp │ │ │ │ │ ├── GameManager.cpp │ │ │ │ │ ├── Gameplay/ │ │ │ │ │ │ ├── LeaderboardRowWidget.cpp │ │ │ │ │ │ ├── LeaderboardWidget.cpp │ │ │ │ │ │ ├── ParallaxBackground.cpp │ │ │ │ │ │ ├── RespawnWidget.cpp │ │ │ │ │ │ └── UsernameChooserWidget.cpp │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── SpacetimeDBClient.g.cpp │ │ │ │ │ │ └── Tables/ │ │ │ │ │ │ ├── CircleTable.g.cpp │ │ │ │ │ │ ├── ConfigTable.g.cpp │ │ │ │ │ │ ├── ConsumeEntityEventTable.g.cpp │ │ │ │ │ │ ├── EntityTable.g.cpp │ │ │ │ │ │ ├── FoodTable.g.cpp │ │ │ │ │ │ └── PlayerTable.g.cpp │ │ │ │ │ └── PlayerPawn.cpp │ │ │ │ ├── Public/ │ │ │ │ │ ├── BlackholioGameMode.h │ │ │ │ │ ├── BlackholioPlayerController.h │ │ │ │ │ ├── Circle.h │ │ │ │ │ ├── DbVector2.h │ │ │ │ │ ├── Entity.h │ │ │ │ │ ├── Food.h │ │ │ │ │ ├── GameManager.h │ │ │ │ │ ├── Gameplay/ │ │ │ │ │ │ ├── LeaderboardRowWidget.h │ │ │ │ │ │ ├── LeaderboardWidget.h │ │ │ │ │ │ ├── ParallaxBackground.h │ │ │ │ │ │ ├── RespawnWidget.h │ │ │ │ │ │ └── UsernameChooserWidget.h │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── ReducerBase.g.h │ │ │ │ │ │ ├── Reducers/ │ │ │ │ │ │ │ ├── EnterGame.g.h │ │ │ │ │ │ │ ├── PlayerSplit.g.h │ │ │ │ │ │ │ ├── Respawn.g.h │ │ │ │ │ │ │ ├── Suicide.g.h │ │ │ │ │ │ │ └── UpdatePlayerInput.g.h │ │ │ │ │ │ ├── SpacetimeDBClient.g.h │ │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ │ ├── CircleTable.g.h │ │ │ │ │ │ │ ├── ConfigTable.g.h │ │ │ │ │ │ │ ├── ConsumeEntityEventTable.g.h │ │ │ │ │ │ │ ├── EntityTable.g.h │ │ │ │ │ │ │ ├── FoodTable.g.h │ │ │ │ │ │ │ └── PlayerTable.g.h │ │ │ │ │ │ └── Types/ │ │ │ │ │ │ ├── CircleDecayTimerType.g.h │ │ │ │ │ │ ├── CircleRecombineTimerType.g.h │ │ │ │ │ │ ├── CircleType.g.h │ │ │ │ │ │ ├── ConfigType.g.h │ │ │ │ │ │ ├── ConsumeEntityEventType.g.h │ │ │ │ │ │ ├── ConsumeEntityTimerType.g.h │ │ │ │ │ │ ├── DbVector2Type.g.h │ │ │ │ │ │ ├── EntityType.g.h │ │ │ │ │ │ ├── FoodType.g.h │ │ │ │ │ │ ├── MoveAllPlayersTimerType.g.h │ │ │ │ │ │ ├── PlayerType.g.h │ │ │ │ │ │ └── SpawnFoodTimerType.g.h │ │ │ │ │ └── PlayerPawn.h │ │ │ │ ├── client_unreal.Build.cs │ │ │ │ ├── client_unreal.cpp │ │ │ │ └── client_unreal.h │ │ │ ├── client_unreal.Target.cs │ │ │ └── client_unrealEditor.Target.cs │ │ └── client_unreal.uproject │ ├── server-cpp/ │ │ └── spacetimedb/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ └── src/ │ │ └── lib.cpp │ ├── server-csharp/ │ │ ├── .gitignore │ │ ├── DbVector2.cs │ │ ├── Lib.cs │ │ ├── StdbModule.csproj │ │ ├── generate.bat │ │ ├── generate.sh │ │ ├── logs.sh │ │ ├── publish.bat │ │ ├── publish.sh │ │ └── write-nuget-config.sh │ └── server-rust/ │ ├── .gitignore │ ├── Cargo.toml │ ├── generate.bat │ ├── generate.sh │ ├── logs.bat │ ├── logs.sh │ ├── publish.bat │ ├── publish.sh │ └── src/ │ ├── lib.rs │ └── math.rs ├── docker-compose.yml ├── docs/ │ ├── .editorconfig │ ├── .gitignore │ ├── DEVELOP.md │ ├── LICENSE.txt │ ├── README.md │ ├── STYLE.md │ ├── docs/ │ │ ├── 00000-ask-ai/ │ │ │ ├── 00100-ask-ai.mdx │ │ │ └── _category_.json │ │ ├── 00100-intro/ │ │ │ ├── 00100-getting-started/ │ │ │ │ ├── 00100-getting-started.md │ │ │ │ ├── 00200-what-is-spacetimedb.md │ │ │ │ ├── 00250-zen-of-spacetimedb.md │ │ │ │ ├── 00300-language-support.md │ │ │ │ ├── 00400-key-architecture.md │ │ │ │ ├── 00500-faq.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-quickstarts/ │ │ │ │ ├── 00100-react.md │ │ │ │ ├── 00150-nextjs.md │ │ │ │ ├── 00150-vue.md │ │ │ │ ├── 00155-nuxt.md │ │ │ │ ├── 00160-svelte.md │ │ │ │ ├── 00165-angular.md │ │ │ │ ├── 00170-tanstack.md │ │ │ │ ├── 00175-remix.md │ │ │ │ ├── 00180-browser.md │ │ │ │ ├── 00250-bun.md │ │ │ │ ├── 00275-deno.md │ │ │ │ ├── 00300-nodejs.md │ │ │ │ ├── 00400-typescript.md │ │ │ │ ├── 00500-rust.md │ │ │ │ ├── 00600-c-sharp.md │ │ │ │ ├── 00700-cpp.md │ │ │ │ └── _category_.json │ │ │ ├── 00300-tutorials/ │ │ │ │ ├── 00100-chat-app.md │ │ │ │ ├── 00300-unity-tutorial/ │ │ │ │ │ ├── 00200-part-1.md │ │ │ │ │ ├── 00300-part-2.md │ │ │ │ │ ├── 00400-part-3.md │ │ │ │ │ ├── 00500-part-4.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── index.md │ │ │ │ ├── 00400-unreal-tutorial/ │ │ │ │ │ ├── 00200-part-1.md │ │ │ │ │ ├── 00300-part-2.md │ │ │ │ │ ├── 00400-part-3.md │ │ │ │ │ ├── 00500-part-4.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── index.md │ │ │ │ └── _category_.json │ │ │ └── _category_.json │ │ ├── 00200-core-concepts/ │ │ │ ├── 00000-index.md │ │ │ ├── 00100-databases/ │ │ │ │ ├── 00100-transactions-atomicity.md │ │ │ │ ├── 00200-spacetime-dev.md │ │ │ │ ├── 00300-spacetime-publish.md │ │ │ │ ├── 00500-cheat-sheet.md │ │ │ │ ├── 00500-migrations/ │ │ │ │ │ ├── 00200-automatic-migrations.md │ │ │ │ │ ├── 00300-incremental-migrations.md │ │ │ │ │ └── _category_.json │ │ │ │ └── _category_.json │ │ │ ├── 00100-databases.md │ │ │ ├── 00200-functions/ │ │ │ │ ├── 00300-reducers/ │ │ │ │ │ ├── 00300-reducers.md │ │ │ │ │ ├── 00400-reducer-context.md │ │ │ │ │ ├── 00500-lifecycle.md │ │ │ │ │ ├── 00600-error-handling.md │ │ │ │ │ └── _category_.json │ │ │ │ ├── 00400-procedures.md │ │ │ │ ├── 00500-views.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-functions.md │ │ │ ├── 00300-tables/ │ │ │ │ ├── 00200-column-types.md │ │ │ │ ├── 00210-file-storage.md │ │ │ │ ├── 00230-auto-increment.md │ │ │ │ ├── 00240-constraints.md │ │ │ │ ├── 00250-default-values.md │ │ │ │ ├── 00300-indexes.md │ │ │ │ ├── 00400-access-permissions.md │ │ │ │ ├── 00500-schedule-tables.md │ │ │ │ ├── 00550-event-tables.md │ │ │ │ ├── 00600-performance.md │ │ │ │ └── _category_.json │ │ │ ├── 00300-tables.md │ │ │ ├── 00400-subscriptions/ │ │ │ │ ├── 00200-subscription-semantics.md │ │ │ │ └── _category_.json │ │ │ ├── 00400-subscriptions.md │ │ │ ├── 00500-authentication/ │ │ │ │ ├── 00100-spacetimeauth/ │ │ │ │ │ ├── 00200-creating-a-project.md │ │ │ │ │ ├── 00300-configuring-a-project.md │ │ │ │ │ ├── 00400-testing.md │ │ │ │ │ ├── 00500-react-integration.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── index.md │ │ │ │ ├── 00200-Auth0.md │ │ │ │ ├── 00300-Clerk.md │ │ │ │ └── 00500-usage.md │ │ │ ├── 00500-authentication.md │ │ │ ├── 00600-clients/ │ │ │ │ ├── 00200-codegen.md │ │ │ │ ├── 00300-connection.md │ │ │ │ ├── 00400-sdk-api.md │ │ │ │ ├── 00500-rust-reference.md │ │ │ │ ├── 00600-csharp-reference.md │ │ │ │ ├── 00700-typescript-reference.md │ │ │ │ ├── 00800-unreal-reference.md │ │ │ │ └── _category_.json │ │ │ ├── 00600-clients.md │ │ │ └── _category_.json │ │ └── 00300-resources/ │ │ ├── 00000-index.md │ │ ├── 00100-how-to/ │ │ │ ├── 00100-deploy/ │ │ │ │ ├── 00100-maincloud.md │ │ │ │ ├── 00200-self-hosting.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-pg-wire.md │ │ │ ├── 00300-logging.md │ │ │ ├── 00400-row-level-security.md │ │ │ ├── 00500-reject-client-connections.md │ │ │ ├── 00600-migrating-to-2.0.md │ │ │ ├── 00700-self-hosted-key-rotation.md │ │ │ └── _category_.json │ │ ├── 00200-reference/ │ │ │ ├── 00100-cli-reference/ │ │ │ │ ├── 00100-cli-reference.md │ │ │ │ ├── 00200-standalone-config.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-http-api/ │ │ │ │ ├── 00100-authorization.md │ │ │ │ ├── 00200-identity.md │ │ │ │ ├── 00300-database.md │ │ │ │ └── _category_.json │ │ │ ├── 00300-internals/ │ │ │ │ ├── 00100-module-abi-reference.md │ │ │ │ ├── 00200-sats-json.md │ │ │ │ ├── 00300-bsatn.md │ │ │ │ └── _category_.json │ │ │ ├── 00400-sql-reference.md │ │ │ └── _category_.json │ │ ├── 01000-reference/ │ │ │ └── 00100-cli-reference/ │ │ │ └── 00300-spacetime-json.md │ │ └── _category_.json │ ├── docusaurus.config.ts │ ├── llms/ │ │ ├── docs-benchmark-analysis.md │ │ ├── docs-benchmark-comment.md │ │ ├── docs-benchmark-details.json │ │ ├── docs-benchmark-summary.json │ │ ├── llm-comparison-details.json │ │ ├── llm-comparison-summary.json │ │ ├── oneshot-grades.json │ │ └── oneshot-summary.md │ ├── package.json │ ├── scripts/ │ │ ├── generate-cli-docs.mjs │ │ ├── get-old-docs.sh │ │ └── rewrite-doc-links.mjs │ ├── sidebars.ts │ ├── src/ │ │ ├── client-modules/ │ │ │ ├── fonts.ts │ │ │ └── inkeep-font-override.ts │ │ ├── components/ │ │ │ ├── CardLink.tsx │ │ │ ├── CardLinkGrid.tsx │ │ │ ├── Check.tsx │ │ │ ├── CppModuleVersionNotice.tsx │ │ │ ├── DocsList.tsx │ │ │ ├── InstallCardLink.tsx │ │ │ ├── QuickstartLinks.tsx │ │ │ └── Steps.tsx │ │ ├── css/ │ │ │ ├── custom.css │ │ │ └── typography.css │ │ └── theme/ │ │ ├── DocVersionBanner/ │ │ │ └── index.tsx │ │ └── NavbarItem/ │ │ └── NavbarNavLink.tsx │ ├── static/ │ │ ├── .nojekyll │ │ ├── ai-rules/ │ │ │ ├── spacetimedb-csharp.mdc │ │ │ ├── spacetimedb-migration-2.0.mdc │ │ │ ├── spacetimedb-rust.mdc │ │ │ ├── spacetimedb-typescript.mdc │ │ │ └── spacetimedb.mdc │ │ └── llms.md │ ├── test-csharp-snippets/ │ │ ├── Module.cs │ │ └── TestProcedures.csproj │ ├── tsconfig.json │ ├── versioned_docs/ │ │ └── version-1.12.0/ │ │ ├── 00000-ask-ai/ │ │ │ ├── 00100-ask-ai.mdx │ │ │ └── _category_.json │ │ ├── 00100-intro/ │ │ │ ├── 00100-getting-started/ │ │ │ │ ├── 00100-getting-started.md │ │ │ │ ├── 00200-what-is-spacetimedb.md │ │ │ │ ├── 00250-zen-of-spacetimedb.md │ │ │ │ ├── 00300-language-support.md │ │ │ │ ├── 00400-key-architecture.md │ │ │ │ ├── 00500-faq.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-quickstarts/ │ │ │ │ ├── 00100-react.md │ │ │ │ ├── 00150-vue.md │ │ │ │ ├── 00160-svelte.md │ │ │ │ ├── 00400-typescript.md │ │ │ │ ├── 00500-rust.md │ │ │ │ ├── 00600-c-sharp.md │ │ │ │ └── _category_.json │ │ │ ├── 00300-tutorials/ │ │ │ │ ├── 00100-chat-app.md │ │ │ │ ├── 00300-unity-tutorial/ │ │ │ │ │ ├── 00200-part-1.md │ │ │ │ │ ├── 00300-part-2.md │ │ │ │ │ ├── 00400-part-3.md │ │ │ │ │ ├── 00500-part-4.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── index.md │ │ │ │ ├── 00400-unreal-tutorial/ │ │ │ │ │ ├── 00200-part-1.md │ │ │ │ │ ├── 00300-part-2.md │ │ │ │ │ ├── 00400-part-3.md │ │ │ │ │ ├── 00500-part-4.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── index.md │ │ │ │ └── _category_.json │ │ │ └── _category_.json │ │ ├── 00200-core-concepts/ │ │ │ ├── 00000-index.md │ │ │ ├── 00100-databases/ │ │ │ │ ├── 00100-transactions-atomicity.md │ │ │ │ ├── 00200-spacetime-dev.md │ │ │ │ ├── 00300-spacetime-publish.md │ │ │ │ ├── 00500-cheat-sheet.md │ │ │ │ ├── 00500-migrations/ │ │ │ │ │ ├── 00200-automatic-migrations.md │ │ │ │ │ ├── 00300-incremental-migrations.md │ │ │ │ │ └── _category_.json │ │ │ │ └── _category_.json │ │ │ ├── 00100-databases.md │ │ │ ├── 00200-functions/ │ │ │ │ ├── 00300-reducers/ │ │ │ │ │ ├── 00300-reducers.md │ │ │ │ │ ├── 00400-reducer-context.md │ │ │ │ │ ├── 00500-lifecycle.md │ │ │ │ │ ├── 00600-error-handling.md │ │ │ │ │ └── _category_.json │ │ │ │ ├── 00400-procedures.md │ │ │ │ ├── 00500-views.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-functions.md │ │ │ ├── 00300-tables/ │ │ │ │ ├── 00200-column-types.md │ │ │ │ ├── 00210-file-storage.md │ │ │ │ ├── 00230-auto-increment.md │ │ │ │ ├── 00240-constraints.md │ │ │ │ ├── 00250-default-values.md │ │ │ │ ├── 00300-indexes.md │ │ │ │ ├── 00400-access-permissions.md │ │ │ │ ├── 00500-schedule-tables.md │ │ │ │ ├── 00600-performance.md │ │ │ │ └── _category_.json │ │ │ ├── 00300-tables.md │ │ │ ├── 00400-subscriptions/ │ │ │ │ ├── 00200-subscription-semantics.md │ │ │ │ └── _category_.json │ │ │ ├── 00400-subscriptions.md │ │ │ ├── 00500-authentication/ │ │ │ │ ├── 00100-spacetimeauth/ │ │ │ │ │ ├── 00200-creating-a-project.md │ │ │ │ │ ├── 00300-configuring-a-project.md │ │ │ │ │ ├── 00400-testing.md │ │ │ │ │ ├── 00500-react-integration.md │ │ │ │ │ ├── _category_.json │ │ │ │ │ └── index.md │ │ │ │ ├── 00200-Auth0.md │ │ │ │ ├── 00300-Clerk.md │ │ │ │ └── 00500-usage.md │ │ │ ├── 00500-authentication.md │ │ │ ├── 00600-client-sdk-languages/ │ │ │ │ ├── 00200-codegen.md │ │ │ │ ├── 00300-connection.md │ │ │ │ ├── 00400-sdk-api.md │ │ │ │ ├── 00500-rust-reference.md │ │ │ │ ├── 00600-csharp-reference.md │ │ │ │ ├── 00700-typescript-reference.md │ │ │ │ ├── 00800-unreal-reference.md │ │ │ │ └── _category_.json │ │ │ ├── 00600-client-sdk-languages.md │ │ │ └── _category_.json │ │ └── 00300-resources/ │ │ ├── 00000-index.md │ │ ├── 00100-how-to/ │ │ │ ├── 00100-deploy/ │ │ │ │ ├── 00100-maincloud.md │ │ │ │ ├── 00200-self-hosting.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-pg-wire.md │ │ │ ├── 00300-logging.md │ │ │ ├── 00400-row-level-security.md │ │ │ ├── 00500-reject-client-connections.md │ │ │ └── _category_.json │ │ ├── 00200-reference/ │ │ │ ├── 00100-cli-reference/ │ │ │ │ ├── 00100-cli-reference.md │ │ │ │ ├── 00200-standalone-config.md │ │ │ │ └── _category_.json │ │ │ ├── 00200-http-api/ │ │ │ │ ├── 00100-authorization.md │ │ │ │ ├── 00200-identity.md │ │ │ │ ├── 00300-database.md │ │ │ │ └── _category_.json │ │ │ ├── 00300-internals/ │ │ │ │ ├── 00100-module-abi-reference.md │ │ │ │ ├── 00200-sats-json.md │ │ │ │ ├── 00300-bsatn.md │ │ │ │ └── _category_.json │ │ │ ├── 00400-sql-reference.md │ │ │ └── _category_.json │ │ └── _category_.json │ ├── versioned_sidebars/ │ │ └── version-1.12.0-sidebars.json │ └── versions.json ├── eslint.config.js ├── flake.nix ├── git-hooks/ │ ├── hooks/ │ │ ├── applypatch-msg.sample │ │ ├── commit-msg.sample │ │ ├── fsmonitor-watchman.sample │ │ ├── post-update.sample │ │ ├── pre-applypatch.sample │ │ ├── pre-commit │ │ ├── pre-commit.sample │ │ ├── pre-merge-commit.sample │ │ ├── pre-push.sample │ │ ├── pre-rebase.sample │ │ ├── pre-receive.sample │ │ ├── prepare-commit-msg.sample │ │ ├── push-to-checkout.sample │ │ └── update.sample │ └── install-hooks.sh ├── global.json ├── librusty_v8.nix ├── licenses/ │ ├── BSL.txt │ └── apache2.txt ├── modules/ │ ├── Directory.Build.props │ ├── Directory.Build.targets │ ├── benchmarks/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── config.toml │ │ └── src/ │ │ ├── circles.rs │ │ ├── ia_loop.rs │ │ ├── lib.rs │ │ └── synthetic.rs │ ├── benchmarks-cpp/ │ │ ├── CMakeLists.txt │ │ ├── build.bat │ │ └── src/ │ │ ├── circles.cpp │ │ ├── common.h │ │ ├── ia_loop.cpp │ │ ├── lib.cpp │ │ └── synthetic.cpp │ ├── benchmarks-cs/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── benchmarks-cs.csproj │ │ ├── circles.cs │ │ ├── ia_loop.cs │ │ ├── lib.cs │ │ └── synthetic.cs │ ├── benchmarks-ts/ │ │ ├── .gitignore │ │ ├── package.json │ │ ├── src/ │ │ │ ├── circles.ts │ │ │ ├── ia_loop.ts │ │ │ ├── index.ts │ │ │ ├── load.ts │ │ │ ├── schema.ts │ │ │ └── synthetic.ts │ │ └── tsconfig.json │ ├── keynote-benchmarks/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── module-test/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── index.html │ │ ├── protobuf/ │ │ │ └── Test.proto │ │ └── src/ │ │ └── lib.rs │ ├── module-test-cpp/ │ │ ├── CMakeLists.txt │ │ ├── compare_module_schemas.py │ │ ├── compile.bat │ │ └── src/ │ │ └── lib.cpp │ ├── module-test-cs/ │ │ ├── .gitignore │ │ ├── Lib.cs │ │ ├── README.md │ │ ├── module-test-cs.csproj │ │ └── module-test-cs.sln │ ├── module-test-ts/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── perf-test/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── sdk-test/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── sdk-test-connect-disconnect/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── .gitignore │ │ └── lib.rs │ ├── sdk-test-connect-disconnect-cpp/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── src/ │ │ └── lib.cpp │ ├── sdk-test-connect-disconnect-cs/ │ │ ├── .gitignore │ │ ├── Lib.cs │ │ ├── README.md │ │ └── sdk-test-connect-disconnect-cs.csproj │ ├── sdk-test-connect-disconnect-ts/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── sdk-test-cpp/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── compile.bat │ │ └── src/ │ │ └── lib.cpp │ ├── sdk-test-cs/ │ │ ├── .gitignore │ │ ├── Lib.cs │ │ ├── README.md │ │ ├── sdk-test-cs.csproj │ │ └── sdk-test-cs.sln │ ├── sdk-test-event-table/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ ├── sdk-test-procedure/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── sdk-test-procedure-cpp/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── client/ │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── module_bindings/ │ │ │ ├── insert_with_tx_commit_procedure.rs │ │ │ ├── insert_with_tx_rollback_procedure.rs │ │ │ ├── invalid_request_procedure.rs │ │ │ ├── mod.rs │ │ │ ├── my_table_table.rs │ │ │ ├── my_table_type.rs │ │ │ ├── pk_uuid_table.rs │ │ │ ├── pk_uuid_type.rs │ │ │ ├── proc_inserts_into_table.rs │ │ │ ├── proc_inserts_into_type.rs │ │ │ ├── read_my_schema_procedure.rs │ │ │ ├── return_enum_a_procedure.rs │ │ │ ├── return_enum_b_procedure.rs │ │ │ ├── return_enum_type.rs │ │ │ ├── return_primitive_procedure.rs │ │ │ ├── return_struct_procedure.rs │ │ │ ├── return_struct_type.rs │ │ │ ├── schedule_proc_reducer.rs │ │ │ ├── scheduled_proc_procedure.rs │ │ │ ├── scheduled_proc_table_table.rs │ │ │ ├── scheduled_proc_table_type.rs │ │ │ ├── sorted_uuids_insert_procedure.rs │ │ │ ├── test_uuid_counter_procedure.rs │ │ │ ├── test_uuid_ordering_procedure.rs │ │ │ ├── test_uuid_round_trip_procedure.rs │ │ │ ├── test_uuid_v_4_procedure.rs │ │ │ ├── test_uuid_v_7_procedure.rs │ │ │ ├── test_uuid_versions_procedure.rs │ │ │ └── will_panic_procedure.rs │ │ ├── compile.bat │ │ └── src/ │ │ └── lib.cpp │ ├── sdk-test-procedure-ts/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── sdk-test-ts/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── sdk-test-view/ │ │ ├── .cargo/ │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ └── lib.rs │ ├── sdk-test-view-cpp/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── compile.bat │ │ └── src/ │ │ └── lib.cpp │ ├── sdk-test-view-pk/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── lib.rs │ └── sdk-test-view-pk-cs/ │ ├── .gitignore │ ├── Lib.cs │ ├── README.md │ └── sdk-test-view-pk-cs.csproj ├── package.json ├── pnpm-workspace.yaml ├── query-builder-syntax-analysis.md ├── run_standalone_temp.sh ├── rust-toolchain.toml ├── sdks/ │ ├── csharp/ │ │ ├── .github/ │ │ │ └── workflows/ │ │ │ ├── publish-nuget.yml │ │ │ └── repo-migration-notice.yml │ │ ├── .gitignore │ │ ├── .meta-check-ignore │ │ ├── DEVELOP.md │ │ ├── DEVELOP.md.meta │ │ ├── Directory.Build.props │ │ ├── Directory.Build.props.meta │ │ ├── LICENSE.txt.meta │ │ ├── README.dotnet.md │ │ ├── README.dotnet.md.meta │ │ ├── README.md │ │ ├── README.md.meta │ │ ├── SpacetimeDB.ClientSDK.csproj │ │ ├── SpacetimeDB.ClientSDK.csproj.meta │ │ ├── SpacetimeDB.ClientSDK.sln │ │ ├── SpacetimeDB.ClientSDK.sln.meta │ │ ├── after.SpacetimeDB.ClientSDK.sln.targets │ │ ├── after.SpacetimeDB.ClientSDK.sln.targets.meta │ │ ├── examples~/ │ │ │ └── regression-tests/ │ │ │ ├── client/ │ │ │ │ ├── EqualityOperations.cs │ │ │ │ ├── Program.cs │ │ │ │ ├── client.csproj │ │ │ │ └── module_bindings/ │ │ │ │ ├── Procedures/ │ │ │ │ │ ├── AuthenticationCapabilities.g.cs │ │ │ │ │ ├── DanglingTxWarning.g.cs │ │ │ │ │ ├── DocumentationGapChecks.g.cs │ │ │ │ │ ├── InsertWithTxCommit.g.cs │ │ │ │ │ ├── InsertWithTxPanic.g.cs │ │ │ │ │ ├── InsertWithTxRetry.g.cs │ │ │ │ │ ├── InsertWithTxRollback.g.cs │ │ │ │ │ ├── InsertWithTxRollbackResult.g.cs │ │ │ │ │ ├── InvalidHttpRequest.g.cs │ │ │ │ │ ├── ReadMySchemaViaHttp.g.cs │ │ │ │ │ ├── ReturnEnumA.g.cs │ │ │ │ │ ├── ReturnEnumB.g.cs │ │ │ │ │ ├── ReturnPrimitive.g.cs │ │ │ │ │ ├── ReturnStructProcedure.g.cs │ │ │ │ │ ├── ReturnUuid.g.cs │ │ │ │ │ ├── SubscriptionEventOffset.g.cs │ │ │ │ │ ├── TxContextCapabilities.g.cs │ │ │ │ │ └── WillPanic.g.cs │ │ │ │ ├── Reducers/ │ │ │ │ │ ├── Add.g.cs │ │ │ │ │ ├── Delete.g.cs │ │ │ │ │ ├── EmitTestEvent.g.cs │ │ │ │ │ ├── InsertEmptyStringIntoNonNullable.g.cs │ │ │ │ │ ├── InsertNullStringIntoNonNullable.g.cs │ │ │ │ │ ├── InsertNullStringIntoNullable.g.cs │ │ │ │ │ ├── InsertResult.g.cs │ │ │ │ │ ├── InsertViewPkMembership.g.cs │ │ │ │ │ ├── InsertViewPkMembershipSecondary.g.cs │ │ │ │ │ ├── InsertViewPkPlayer.g.cs │ │ │ │ │ ├── InsertWhereTest.g.cs │ │ │ │ │ ├── Noop.g.cs │ │ │ │ │ ├── SetNullableVec.g.cs │ │ │ │ │ ├── ThrowError.g.cs │ │ │ │ │ ├── UpdateViewPkPlayer.g.cs │ │ │ │ │ └── UpdateWhereTest.g.cs │ │ │ │ ├── SpacetimeDBClient.g.cs │ │ │ │ ├── Tables/ │ │ │ │ │ ├── Account.g.cs │ │ │ │ │ ├── Admins.g.cs │ │ │ │ │ ├── AllViewPkPlayers.g.cs │ │ │ │ │ ├── ExampleData.g.cs │ │ │ │ │ ├── FindWhereTest.g.cs │ │ │ │ │ ├── MyAccount.g.cs │ │ │ │ │ ├── MyAccountMissing.g.cs │ │ │ │ │ ├── MyLog.g.cs │ │ │ │ │ ├── MyPlayer.g.cs │ │ │ │ │ ├── MyTable.g.cs │ │ │ │ │ ├── NullStringNonnullable.g.cs │ │ │ │ │ ├── NullStringNullable.g.cs │ │ │ │ │ ├── NullableVec.g.cs │ │ │ │ │ ├── NullableVecView.g.cs │ │ │ │ │ ├── Player.g.cs │ │ │ │ │ ├── PlayerLevel.g.cs │ │ │ │ │ ├── PlayersAtLevelOne.g.cs │ │ │ │ │ ├── RetryLog.g.cs │ │ │ │ │ ├── Score.g.cs │ │ │ │ │ ├── ScoresPlayer123.g.cs │ │ │ │ │ ├── ScoresPlayer123Level5.g.cs │ │ │ │ │ ├── ScoresPlayer123Range.g.cs │ │ │ │ │ ├── SenderViewPkPlayersA.g.cs │ │ │ │ │ ├── SenderViewPkPlayersB.g.cs │ │ │ │ │ ├── TestEvent.g.cs │ │ │ │ │ ├── User.g.cs │ │ │ │ │ ├── UsersAge1865.g.cs │ │ │ │ │ ├── UsersAge18Plus.g.cs │ │ │ │ │ ├── UsersAgeUnder18.g.cs │ │ │ │ │ ├── UsersNamedAlice.g.cs │ │ │ │ │ ├── ViewPkMembership.g.cs │ │ │ │ │ ├── ViewPkMembershipSecondary.g.cs │ │ │ │ │ ├── ViewPkPlayer.g.cs │ │ │ │ │ ├── WhereTest.g.cs │ │ │ │ │ ├── WhereTestQuery.g.cs │ │ │ │ │ └── WhereTestView.g.cs │ │ │ │ └── Types/ │ │ │ │ ├── Account.g.cs │ │ │ │ ├── DbVector2.g.cs │ │ │ │ ├── ExampleData.g.cs │ │ │ │ ├── MyLog.g.cs │ │ │ │ ├── MyTable.g.cs │ │ │ │ ├── NullStringNonNullable.g.cs │ │ │ │ ├── NullStringNullable.g.cs │ │ │ │ ├── NullableVec.g.cs │ │ │ │ ├── Player.g.cs │ │ │ │ ├── PlayerAndLevel.g.cs │ │ │ │ ├── PlayerLevel.g.cs │ │ │ │ ├── RetryLog.g.cs │ │ │ │ ├── ReturnEnum.g.cs │ │ │ │ ├── ReturnStruct.g.cs │ │ │ │ ├── Score.g.cs │ │ │ │ ├── TestEvent.g.cs │ │ │ │ ├── User.g.cs │ │ │ │ ├── ViewPkMembership.g.cs │ │ │ │ ├── ViewPkMembershipSecondary.g.cs │ │ │ │ ├── ViewPkPlayer.g.cs │ │ │ │ └── WhereTest.g.cs │ │ │ ├── procedure-client/ │ │ │ │ ├── EqualityOperations.cs │ │ │ │ ├── Program.cs │ │ │ │ ├── README.md │ │ │ │ ├── client.csproj │ │ │ │ └── module_bindings/ │ │ │ │ ├── Procedures/ │ │ │ │ │ ├── InsertWithTxCommit.g.cs │ │ │ │ │ ├── InsertWithTxRollback.g.cs │ │ │ │ │ ├── InvalidRequest.g.cs │ │ │ │ │ ├── ReadMySchema.g.cs │ │ │ │ │ ├── ReturnEnumA.g.cs │ │ │ │ │ ├── ReturnEnumB.g.cs │ │ │ │ │ ├── ReturnPrimitive.g.cs │ │ │ │ │ ├── ReturnStruct.g.cs │ │ │ │ │ ├── SortedUuidsInsert.g.cs │ │ │ │ │ └── WillPanic.g.cs │ │ │ │ ├── Reducers/ │ │ │ │ │ └── ScheduleProc.g.cs │ │ │ │ ├── SpacetimeDBClient.g.cs │ │ │ │ ├── Tables/ │ │ │ │ │ ├── MyTable.g.cs │ │ │ │ │ ├── PkUuid.g.cs │ │ │ │ │ └── ProcInsertsInto.g.cs │ │ │ │ └── Types/ │ │ │ │ ├── MyTable.g.cs │ │ │ │ ├── PkUuid.g.cs │ │ │ │ ├── ProcInsertsInto.g.cs │ │ │ │ ├── ReturnEnum.g.cs │ │ │ │ ├── ReturnStruct.g.cs │ │ │ │ └── ScheduledProcTable.g.cs │ │ │ ├── republishing/ │ │ │ │ ├── client/ │ │ │ │ │ ├── Program.cs │ │ │ │ │ ├── client.csproj │ │ │ │ │ └── module_bindings/ │ │ │ │ │ ├── Reducers/ │ │ │ │ │ │ └── Insert.g.cs │ │ │ │ │ ├── SpacetimeDBClient.g.cs │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ └── ExampleData.g.cs │ │ │ │ │ └── Types/ │ │ │ │ │ ├── ExampleData.g.cs │ │ │ │ │ ├── MyEnum.g.cs │ │ │ │ │ └── MyStruct.g.cs │ │ │ │ ├── server-initial/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Lib.cs │ │ │ │ │ └── StdbModule.csproj │ │ │ │ └── server-republish/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Lib.cs │ │ │ │ └── StdbModule.csproj │ │ │ ├── server/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Lib.cs │ │ │ │ └── StdbModule.csproj │ │ │ └── shared/ │ │ │ └── RegressionTestHarness.cs │ │ ├── logo.png.meta │ │ ├── package.json │ │ ├── package.json.meta │ │ ├── packages/ │ │ │ └── .gitignore │ │ ├── src/ │ │ │ ├── AuthToken.cs │ │ │ ├── AuthToken.cs.meta │ │ │ ├── BSATNHelpers.cs │ │ │ ├── BSATNHelpers.cs.meta │ │ │ ├── Compression.cs │ │ │ ├── Compression.cs.meta │ │ │ ├── CompressionHelpers.cs │ │ │ ├── CompressionHelpers.cs.meta │ │ │ ├── ConsoleLogger.cs │ │ │ ├── ConsoleLogger.cs.meta │ │ │ ├── Event.cs │ │ │ ├── Event.cs.meta │ │ │ ├── EventHandling/ │ │ │ │ ├── AbstractEventHandler.cs │ │ │ │ ├── AbstractEventHandler.cs.meta │ │ │ │ ├── EventListeners.cs │ │ │ │ ├── EventListeners.cs.meta │ │ │ │ ├── ListExtensions.cs │ │ │ │ └── ListExtensions.cs.meta │ │ │ ├── EventHandling.meta │ │ │ ├── Exceptions.cs │ │ │ ├── Exceptions.cs.meta │ │ │ ├── ISpacetimeDBLogger.cs │ │ │ ├── ISpacetimeDBLogger.cs.meta │ │ │ ├── ListStream.cs │ │ │ ├── ListStream.cs.meta │ │ │ ├── MultiDictionary.cs │ │ │ ├── MultiDictionary.cs.meta │ │ │ ├── Plugins/ │ │ │ │ ├── WebSocket.jslib │ │ │ │ └── WebSocket.jslib.meta │ │ │ ├── Plugins.meta │ │ │ ├── ProcedureCallbacks.cs │ │ │ ├── ProcedureCallbacks.cs.meta │ │ │ ├── RemoteTablesBase.cs │ │ │ ├── RemoteTablesBase.cs.meta │ │ │ ├── SpacetimeDB/ │ │ │ │ ├── ClientApi/ │ │ │ │ │ ├── BsatnRowList.g.cs │ │ │ │ │ ├── BsatnRowList.g.cs.meta │ │ │ │ │ ├── CallProcedure.g.cs │ │ │ │ │ ├── CallProcedure.g.cs.meta │ │ │ │ │ ├── CallReducer.g.cs │ │ │ │ │ ├── CallReducer.g.cs.meta │ │ │ │ │ ├── ClientMessage.g.cs │ │ │ │ │ ├── ClientMessage.g.cs.meta │ │ │ │ │ ├── EnergyQuanta.g.cs │ │ │ │ │ ├── EnergyQuanta.g.cs.meta │ │ │ │ │ ├── EventTableRows.g.cs │ │ │ │ │ ├── EventTableRows.g.cs.meta │ │ │ │ │ ├── InitialConnection.g.cs │ │ │ │ │ ├── InitialConnection.g.cs.meta │ │ │ │ │ ├── OneOffQuery.g.cs │ │ │ │ │ ├── OneOffQuery.g.cs.meta │ │ │ │ │ ├── OneOffQueryResult.g.cs │ │ │ │ │ ├── OneOffQueryResult.g.cs.meta │ │ │ │ │ ├── PersistentTableRows.g.cs │ │ │ │ │ ├── PersistentTableRows.g.cs.meta │ │ │ │ │ ├── ProcedureResult.g.cs │ │ │ │ │ ├── ProcedureResult.g.cs.meta │ │ │ │ │ ├── ProcedureStatus.g.cs │ │ │ │ │ ├── ProcedureStatus.g.cs.meta │ │ │ │ │ ├── QueryRows.g.cs │ │ │ │ │ ├── QueryRows.g.cs.meta │ │ │ │ │ ├── QuerySetId.g.cs │ │ │ │ │ ├── QuerySetId.g.cs.meta │ │ │ │ │ ├── QuerySetUpdate.g.cs │ │ │ │ │ ├── QuerySetUpdate.g.cs.meta │ │ │ │ │ ├── ReducerCallInfo.g.cs │ │ │ │ │ ├── ReducerCallInfo.g.cs.meta │ │ │ │ │ ├── ReducerOk.g.cs │ │ │ │ │ ├── ReducerOk.g.cs.meta │ │ │ │ │ ├── ReducerOutcome.g.cs │ │ │ │ │ ├── ReducerOutcome.g.cs.meta │ │ │ │ │ ├── ReducerResult.g.cs │ │ │ │ │ ├── ReducerResult.g.cs.meta │ │ │ │ │ ├── RowSizeHint.g.cs │ │ │ │ │ ├── RowSizeHint.g.cs.meta │ │ │ │ │ ├── ServerMessage.g.cs │ │ │ │ │ ├── ServerMessage.g.cs.meta │ │ │ │ │ ├── SingleTableRows.g.cs │ │ │ │ │ ├── SingleTableRows.g.cs.meta │ │ │ │ │ ├── Subscribe.g.cs │ │ │ │ │ ├── Subscribe.g.cs.meta │ │ │ │ │ ├── SubscribeApplied.g.cs │ │ │ │ │ ├── SubscribeApplied.g.cs.meta │ │ │ │ │ ├── SubscriptionError.g.cs │ │ │ │ │ ├── SubscriptionError.g.cs.meta │ │ │ │ │ ├── TableUpdate.g.cs │ │ │ │ │ ├── TableUpdate.g.cs.meta │ │ │ │ │ ├── TableUpdateRows.g.cs │ │ │ │ │ ├── TableUpdateRows.g.cs.meta │ │ │ │ │ ├── TransactionUpdate.g.cs │ │ │ │ │ ├── TransactionUpdate.g.cs.meta │ │ │ │ │ ├── Unsubscribe.g.cs │ │ │ │ │ ├── Unsubscribe.g.cs.meta │ │ │ │ │ ├── UnsubscribeApplied.g.cs │ │ │ │ │ ├── UnsubscribeApplied.g.cs.meta │ │ │ │ │ ├── UnsubscribeFlags.g.cs │ │ │ │ │ └── UnsubscribeFlags.g.cs.meta │ │ │ │ └── ClientApi.meta │ │ │ ├── SpacetimeDB.meta │ │ │ ├── SpacetimeDBClient.cs │ │ │ ├── SpacetimeDBClient.cs.meta │ │ │ ├── SpacetimeDBNetworkManager.cs │ │ │ ├── SpacetimeDBNetworkManager.cs.meta │ │ │ ├── Stats.cs │ │ │ ├── Stats.cs.meta │ │ │ ├── Table.cs │ │ │ ├── Table.cs.meta │ │ │ ├── UnityDebugLogger.cs │ │ │ ├── UnityDebugLogger.cs.meta │ │ │ ├── Utils.cs │ │ │ ├── Utils.cs.meta │ │ │ ├── WebSocket.cs │ │ │ ├── WebSocket.cs.meta │ │ │ ├── com.clockworklabs.spacetimedbsdk.asmdef │ │ │ ├── com.clockworklabs.spacetimedbsdk.asmdef.meta │ │ │ ├── csc.rsp │ │ │ └── csc.rsp.meta │ │ ├── src.meta │ │ ├── tests~/ │ │ │ ├── MultiDictionaryTests.cs │ │ │ ├── QueryBuilderTests.cs │ │ │ ├── README.md │ │ │ ├── SnapshotTests.VerifySampleDump_dumpName=LegacySubscribeAll.verified.txt │ │ │ ├── SnapshotTests.VerifySampleDump_dumpName=SubscribeApplied.verified.txt │ │ │ ├── SnapshotTests.cs │ │ │ ├── Tests.cs │ │ │ ├── VerifyInit.cs │ │ │ └── tests.csproj │ │ ├── tools~/ │ │ │ ├── gen-client-api.bat │ │ │ ├── gen-client-api.sh │ │ │ ├── gen-quickstart.sh │ │ │ ├── gen-regression-tests.sh │ │ │ ├── run-regression-tests.sh │ │ │ ├── update-against-stdb.sh │ │ │ ├── upgrade-version.py │ │ │ └── write-nuget-config.sh │ │ └── unity-meta-skeleton~/ │ │ ├── spacetimedb.bsatn.runtime/ │ │ │ ├── analyzers/ │ │ │ │ ├── dotnet/ │ │ │ │ │ ├── cs/ │ │ │ │ │ │ └── SpacetimeDB.BSATN.Codegen.dll.meta │ │ │ │ │ └── cs.meta │ │ │ │ └── dotnet.meta │ │ │ ├── analyzers.meta │ │ │ ├── lib/ │ │ │ │ ├── net8.0/ │ │ │ │ │ └── SpacetimeDB.BSATN.Runtime.dll.meta │ │ │ │ ├── net8.0.meta │ │ │ │ ├── netstandard2.1/ │ │ │ │ │ └── SpacetimeDB.BSATN.Runtime.dll.meta │ │ │ │ └── netstandard2.1.meta │ │ │ ├── lib.meta │ │ │ └── version.meta │ │ ├── spacetimedb.bsatn.runtime.meta │ │ ├── spacetimedb.runtime/ │ │ │ ├── analyzers/ │ │ │ │ ├── dotnet/ │ │ │ │ │ ├── cs/ │ │ │ │ │ │ └── SpacetimeDB.Codegen.dll.meta │ │ │ │ │ └── cs.meta │ │ │ │ └── dotnet.meta │ │ │ ├── analyzers.meta │ │ │ ├── lib/ │ │ │ │ └── net8.0.meta │ │ │ ├── lib.meta │ │ │ └── version.meta │ │ └── spacetimedb.runtime.meta │ ├── rust/ │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── callbacks.rs │ │ │ ├── client_cache.rs │ │ │ ├── compression.rs │ │ │ ├── credentials.rs │ │ │ ├── db_connection.rs │ │ │ ├── db_context.rs │ │ │ ├── error.rs │ │ │ ├── event.rs │ │ │ ├── lib.rs │ │ │ ├── metrics.rs │ │ │ ├── spacetime_module.rs │ │ │ ├── subscription.rs │ │ │ ├── table.rs │ │ │ └── websocket.rs │ │ └── tests/ │ │ ├── connect_disconnect_client/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── module_bindings/ │ │ │ ├── connected_table.rs │ │ │ ├── connected_type.rs │ │ │ ├── disconnected_table.rs │ │ │ ├── disconnected_type.rs │ │ │ ├── identity_connected_reducer.rs │ │ │ ├── identity_disconnected_reducer.rs │ │ │ └── mod.rs │ │ ├── event-table-client/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── module_bindings/ │ │ │ ├── emit_multiple_test_events_reducer.rs │ │ │ ├── emit_test_event_reducer.rs │ │ │ ├── mod.rs │ │ │ ├── noop_reducer.rs │ │ │ ├── test_event_table.rs │ │ │ └── test_event_type.rs │ │ ├── procedure-client/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── module_bindings/ │ │ │ ├── insert_with_tx_commit_procedure.rs │ │ │ ├── insert_with_tx_rollback_procedure.rs │ │ │ ├── invalid_request_procedure.rs │ │ │ ├── mod.rs │ │ │ ├── my_table_table.rs │ │ │ ├── my_table_type.rs │ │ │ ├── pk_uuid_table.rs │ │ │ ├── pk_uuid_type.rs │ │ │ ├── proc_inserts_into_table.rs │ │ │ ├── proc_inserts_into_type.rs │ │ │ ├── read_my_schema_procedure.rs │ │ │ ├── return_enum_a_procedure.rs │ │ │ ├── return_enum_b_procedure.rs │ │ │ ├── return_enum_type.rs │ │ │ ├── return_primitive_procedure.rs │ │ │ ├── return_struct_procedure.rs │ │ │ ├── return_struct_type.rs │ │ │ ├── schedule_proc_reducer.rs │ │ │ ├── scheduled_proc_procedure.rs │ │ │ ├── scheduled_proc_table_table.rs │ │ │ ├── scheduled_proc_table_type.rs │ │ │ ├── sorted_uuids_insert_procedure.rs │ │ │ └── will_panic_procedure.rs │ │ ├── test-client/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ ├── module_bindings/ │ │ │ │ ├── b_tree_u_32_type.rs │ │ │ │ ├── btree_u_32_table.rs │ │ │ │ ├── byte_struct_type.rs │ │ │ │ ├── delete_from_btree_u_32_reducer.rs │ │ │ │ ├── delete_large_table_reducer.rs │ │ │ │ ├── delete_pk_bool_reducer.rs │ │ │ │ ├── delete_pk_connection_id_reducer.rs │ │ │ │ ├── delete_pk_i_128_reducer.rs │ │ │ │ ├── delete_pk_i_16_reducer.rs │ │ │ │ ├── delete_pk_i_256_reducer.rs │ │ │ │ ├── delete_pk_i_32_reducer.rs │ │ │ │ ├── delete_pk_i_64_reducer.rs │ │ │ │ ├── delete_pk_i_8_reducer.rs │ │ │ │ ├── delete_pk_identity_reducer.rs │ │ │ │ ├── delete_pk_string_reducer.rs │ │ │ │ ├── delete_pk_u_128_reducer.rs │ │ │ │ ├── delete_pk_u_16_reducer.rs │ │ │ │ ├── delete_pk_u_256_reducer.rs │ │ │ │ ├── delete_pk_u_32_insert_pk_u_32_two_reducer.rs │ │ │ │ ├── delete_pk_u_32_reducer.rs │ │ │ │ ├── delete_pk_u_32_two_reducer.rs │ │ │ │ ├── delete_pk_u_64_reducer.rs │ │ │ │ ├── delete_pk_u_8_reducer.rs │ │ │ │ ├── delete_pk_uuid_reducer.rs │ │ │ │ ├── delete_unique_bool_reducer.rs │ │ │ │ ├── delete_unique_connection_id_reducer.rs │ │ │ │ ├── delete_unique_i_128_reducer.rs │ │ │ │ ├── delete_unique_i_16_reducer.rs │ │ │ │ ├── delete_unique_i_256_reducer.rs │ │ │ │ ├── delete_unique_i_32_reducer.rs │ │ │ │ ├── delete_unique_i_64_reducer.rs │ │ │ │ ├── delete_unique_i_8_reducer.rs │ │ │ │ ├── delete_unique_identity_reducer.rs │ │ │ │ ├── delete_unique_string_reducer.rs │ │ │ │ ├── delete_unique_u_128_reducer.rs │ │ │ │ ├── delete_unique_u_16_reducer.rs │ │ │ │ ├── delete_unique_u_256_reducer.rs │ │ │ │ ├── delete_unique_u_32_reducer.rs │ │ │ │ ├── delete_unique_u_64_reducer.rs │ │ │ │ ├── delete_unique_u_8_reducer.rs │ │ │ │ ├── delete_unique_uuid_reducer.rs │ │ │ │ ├── enum_with_payload_type.rs │ │ │ │ ├── every_primitive_struct_type.rs │ │ │ │ ├── every_vec_struct_type.rs │ │ │ │ ├── indexed_simple_enum_table.rs │ │ │ │ ├── indexed_simple_enum_type.rs │ │ │ │ ├── indexed_table_2_table.rs │ │ │ │ ├── indexed_table_2_type.rs │ │ │ │ ├── indexed_table_table.rs │ │ │ │ ├── indexed_table_type.rs │ │ │ │ ├── insert_call_timestamp_reducer.rs │ │ │ │ ├── insert_call_uuid_v_4_reducer.rs │ │ │ │ ├── insert_call_uuid_v_7_reducer.rs │ │ │ │ ├── insert_caller_one_connection_id_reducer.rs │ │ │ │ ├── insert_caller_one_identity_reducer.rs │ │ │ │ ├── insert_caller_pk_connection_id_reducer.rs │ │ │ │ ├── insert_caller_pk_identity_reducer.rs │ │ │ │ ├── insert_caller_unique_connection_id_reducer.rs │ │ │ │ ├── insert_caller_unique_identity_reducer.rs │ │ │ │ ├── insert_caller_vec_connection_id_reducer.rs │ │ │ │ ├── insert_caller_vec_identity_reducer.rs │ │ │ │ ├── insert_into_btree_u_32_reducer.rs │ │ │ │ ├── insert_into_indexed_simple_enum_reducer.rs │ │ │ │ ├── insert_into_pk_btree_u_32_reducer.rs │ │ │ │ ├── insert_large_table_reducer.rs │ │ │ │ ├── insert_one_bool_reducer.rs │ │ │ │ ├── insert_one_byte_struct_reducer.rs │ │ │ │ ├── insert_one_connection_id_reducer.rs │ │ │ │ ├── insert_one_enum_with_payload_reducer.rs │ │ │ │ ├── insert_one_every_primitive_struct_reducer.rs │ │ │ │ ├── insert_one_every_vec_struct_reducer.rs │ │ │ │ ├── insert_one_f_32_reducer.rs │ │ │ │ ├── insert_one_f_64_reducer.rs │ │ │ │ ├── insert_one_i_128_reducer.rs │ │ │ │ ├── insert_one_i_16_reducer.rs │ │ │ │ ├── insert_one_i_256_reducer.rs │ │ │ │ ├── insert_one_i_32_reducer.rs │ │ │ │ ├── insert_one_i_64_reducer.rs │ │ │ │ ├── insert_one_i_8_reducer.rs │ │ │ │ ├── insert_one_identity_reducer.rs │ │ │ │ ├── insert_one_simple_enum_reducer.rs │ │ │ │ ├── insert_one_string_reducer.rs │ │ │ │ ├── insert_one_timestamp_reducer.rs │ │ │ │ ├── insert_one_u_128_reducer.rs │ │ │ │ ├── insert_one_u_16_reducer.rs │ │ │ │ ├── insert_one_u_256_reducer.rs │ │ │ │ ├── insert_one_u_32_reducer.rs │ │ │ │ ├── insert_one_u_64_reducer.rs │ │ │ │ ├── insert_one_u_8_reducer.rs │ │ │ │ ├── insert_one_unit_struct_reducer.rs │ │ │ │ ├── insert_one_uuid_reducer.rs │ │ │ │ ├── insert_option_every_primitive_struct_reducer.rs │ │ │ │ ├── insert_option_i_32_reducer.rs │ │ │ │ ├── insert_option_identity_reducer.rs │ │ │ │ ├── insert_option_simple_enum_reducer.rs │ │ │ │ ├── insert_option_string_reducer.rs │ │ │ │ ├── insert_option_uuid_reducer.rs │ │ │ │ ├── insert_option_vec_option_i_32_reducer.rs │ │ │ │ ├── insert_pk_bool_reducer.rs │ │ │ │ ├── insert_pk_connection_id_reducer.rs │ │ │ │ ├── insert_pk_i_128_reducer.rs │ │ │ │ ├── insert_pk_i_16_reducer.rs │ │ │ │ ├── insert_pk_i_256_reducer.rs │ │ │ │ ├── insert_pk_i_32_reducer.rs │ │ │ │ ├── insert_pk_i_64_reducer.rs │ │ │ │ ├── insert_pk_i_8_reducer.rs │ │ │ │ ├── insert_pk_identity_reducer.rs │ │ │ │ ├── insert_pk_simple_enum_reducer.rs │ │ │ │ ├── insert_pk_string_reducer.rs │ │ │ │ ├── insert_pk_u_128_reducer.rs │ │ │ │ ├── insert_pk_u_16_reducer.rs │ │ │ │ ├── insert_pk_u_256_reducer.rs │ │ │ │ ├── insert_pk_u_32_reducer.rs │ │ │ │ ├── insert_pk_u_32_two_reducer.rs │ │ │ │ ├── insert_pk_u_64_reducer.rs │ │ │ │ ├── insert_pk_u_8_reducer.rs │ │ │ │ ├── insert_pk_uuid_reducer.rs │ │ │ │ ├── insert_primitives_as_strings_reducer.rs │ │ │ │ ├── insert_result_every_primitive_struct_string_reducer.rs │ │ │ │ ├── insert_result_i_32_string_reducer.rs │ │ │ │ ├── insert_result_identity_string_reducer.rs │ │ │ │ ├── insert_result_simple_enum_i_32_reducer.rs │ │ │ │ ├── insert_result_string_i_32_reducer.rs │ │ │ │ ├── insert_result_vec_i_32_string_reducer.rs │ │ │ │ ├── insert_table_holds_table_reducer.rs │ │ │ │ ├── insert_unique_bool_reducer.rs │ │ │ │ ├── insert_unique_connection_id_reducer.rs │ │ │ │ ├── insert_unique_i_128_reducer.rs │ │ │ │ ├── insert_unique_i_16_reducer.rs │ │ │ │ ├── insert_unique_i_256_reducer.rs │ │ │ │ ├── insert_unique_i_32_reducer.rs │ │ │ │ ├── insert_unique_i_64_reducer.rs │ │ │ │ ├── insert_unique_i_8_reducer.rs │ │ │ │ ├── insert_unique_identity_reducer.rs │ │ │ │ ├── insert_unique_string_reducer.rs │ │ │ │ ├── insert_unique_u_128_reducer.rs │ │ │ │ ├── insert_unique_u_16_reducer.rs │ │ │ │ ├── insert_unique_u_256_reducer.rs │ │ │ │ ├── insert_unique_u_32_reducer.rs │ │ │ │ ├── insert_unique_u_32_update_pk_u_32_reducer.rs │ │ │ │ ├── insert_unique_u_64_reducer.rs │ │ │ │ ├── insert_unique_u_8_reducer.rs │ │ │ │ ├── insert_unique_uuid_reducer.rs │ │ │ │ ├── insert_user_reducer.rs │ │ │ │ ├── insert_vec_bool_reducer.rs │ │ │ │ ├── insert_vec_byte_struct_reducer.rs │ │ │ │ ├── insert_vec_connection_id_reducer.rs │ │ │ │ ├── insert_vec_enum_with_payload_reducer.rs │ │ │ │ ├── insert_vec_every_primitive_struct_reducer.rs │ │ │ │ ├── insert_vec_every_vec_struct_reducer.rs │ │ │ │ ├── insert_vec_f_32_reducer.rs │ │ │ │ ├── insert_vec_f_64_reducer.rs │ │ │ │ ├── insert_vec_i_128_reducer.rs │ │ │ │ ├── insert_vec_i_16_reducer.rs │ │ │ │ ├── insert_vec_i_256_reducer.rs │ │ │ │ ├── insert_vec_i_32_reducer.rs │ │ │ │ ├── insert_vec_i_64_reducer.rs │ │ │ │ ├── insert_vec_i_8_reducer.rs │ │ │ │ ├── insert_vec_identity_reducer.rs │ │ │ │ ├── insert_vec_simple_enum_reducer.rs │ │ │ │ ├── insert_vec_string_reducer.rs │ │ │ │ ├── insert_vec_timestamp_reducer.rs │ │ │ │ ├── insert_vec_u_128_reducer.rs │ │ │ │ ├── insert_vec_u_16_reducer.rs │ │ │ │ ├── insert_vec_u_256_reducer.rs │ │ │ │ ├── insert_vec_u_32_reducer.rs │ │ │ │ ├── insert_vec_u_64_reducer.rs │ │ │ │ ├── insert_vec_u_8_reducer.rs │ │ │ │ ├── insert_vec_unit_struct_reducer.rs │ │ │ │ ├── insert_vec_uuid_reducer.rs │ │ │ │ ├── large_table_table.rs │ │ │ │ ├── large_table_type.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── no_op_succeeds_reducer.rs │ │ │ │ ├── one_bool_table.rs │ │ │ │ ├── one_bool_type.rs │ │ │ │ ├── one_byte_struct_table.rs │ │ │ │ ├── one_byte_struct_type.rs │ │ │ │ ├── one_connection_id_table.rs │ │ │ │ ├── one_connection_id_type.rs │ │ │ │ ├── one_enum_with_payload_table.rs │ │ │ │ ├── one_enum_with_payload_type.rs │ │ │ │ ├── one_every_primitive_struct_table.rs │ │ │ │ ├── one_every_primitive_struct_type.rs │ │ │ │ ├── one_every_vec_struct_table.rs │ │ │ │ ├── one_every_vec_struct_type.rs │ │ │ │ ├── one_f_32_table.rs │ │ │ │ ├── one_f_32_type.rs │ │ │ │ ├── one_f_64_table.rs │ │ │ │ ├── one_f_64_type.rs │ │ │ │ ├── one_i_128_table.rs │ │ │ │ ├── one_i_128_type.rs │ │ │ │ ├── one_i_16_table.rs │ │ │ │ ├── one_i_16_type.rs │ │ │ │ ├── one_i_256_table.rs │ │ │ │ ├── one_i_256_type.rs │ │ │ │ ├── one_i_32_table.rs │ │ │ │ ├── one_i_32_type.rs │ │ │ │ ├── one_i_64_table.rs │ │ │ │ ├── one_i_64_type.rs │ │ │ │ ├── one_i_8_table.rs │ │ │ │ ├── one_i_8_type.rs │ │ │ │ ├── one_identity_table.rs │ │ │ │ ├── one_identity_type.rs │ │ │ │ ├── one_simple_enum_table.rs │ │ │ │ ├── one_simple_enum_type.rs │ │ │ │ ├── one_string_table.rs │ │ │ │ ├── one_string_type.rs │ │ │ │ ├── one_timestamp_table.rs │ │ │ │ ├── one_timestamp_type.rs │ │ │ │ ├── one_u_128_table.rs │ │ │ │ ├── one_u_128_type.rs │ │ │ │ ├── one_u_16_table.rs │ │ │ │ ├── one_u_16_type.rs │ │ │ │ ├── one_u_256_table.rs │ │ │ │ ├── one_u_256_type.rs │ │ │ │ ├── one_u_32_table.rs │ │ │ │ ├── one_u_32_type.rs │ │ │ │ ├── one_u_64_table.rs │ │ │ │ ├── one_u_64_type.rs │ │ │ │ ├── one_u_8_table.rs │ │ │ │ ├── one_u_8_type.rs │ │ │ │ ├── one_unit_struct_table.rs │ │ │ │ ├── one_unit_struct_type.rs │ │ │ │ ├── one_uuid_table.rs │ │ │ │ ├── one_uuid_type.rs │ │ │ │ ├── option_every_primitive_struct_table.rs │ │ │ │ ├── option_every_primitive_struct_type.rs │ │ │ │ ├── option_i_32_table.rs │ │ │ │ ├── option_i_32_type.rs │ │ │ │ ├── option_identity_table.rs │ │ │ │ ├── option_identity_type.rs │ │ │ │ ├── option_simple_enum_table.rs │ │ │ │ ├── option_simple_enum_type.rs │ │ │ │ ├── option_string_table.rs │ │ │ │ ├── option_string_type.rs │ │ │ │ ├── option_uuid_table.rs │ │ │ │ ├── option_uuid_type.rs │ │ │ │ ├── option_vec_option_i_32_table.rs │ │ │ │ ├── option_vec_option_i_32_type.rs │ │ │ │ ├── pk_bool_table.rs │ │ │ │ ├── pk_bool_type.rs │ │ │ │ ├── pk_connection_id_table.rs │ │ │ │ ├── pk_connection_id_type.rs │ │ │ │ ├── pk_i_128_table.rs │ │ │ │ ├── pk_i_128_type.rs │ │ │ │ ├── pk_i_16_table.rs │ │ │ │ ├── pk_i_16_type.rs │ │ │ │ ├── pk_i_256_table.rs │ │ │ │ ├── pk_i_256_type.rs │ │ │ │ ├── pk_i_32_table.rs │ │ │ │ ├── pk_i_32_type.rs │ │ │ │ ├── pk_i_64_table.rs │ │ │ │ ├── pk_i_64_type.rs │ │ │ │ ├── pk_i_8_table.rs │ │ │ │ ├── pk_i_8_type.rs │ │ │ │ ├── pk_identity_table.rs │ │ │ │ ├── pk_identity_type.rs │ │ │ │ ├── pk_simple_enum_table.rs │ │ │ │ ├── pk_simple_enum_type.rs │ │ │ │ ├── pk_string_table.rs │ │ │ │ ├── pk_string_type.rs │ │ │ │ ├── pk_u_128_table.rs │ │ │ │ ├── pk_u_128_type.rs │ │ │ │ ├── pk_u_16_table.rs │ │ │ │ ├── pk_u_16_type.rs │ │ │ │ ├── pk_u_256_table.rs │ │ │ │ ├── pk_u_256_type.rs │ │ │ │ ├── pk_u_32_table.rs │ │ │ │ ├── pk_u_32_two_table.rs │ │ │ │ ├── pk_u_32_two_type.rs │ │ │ │ ├── pk_u_32_type.rs │ │ │ │ ├── pk_u_64_table.rs │ │ │ │ ├── pk_u_64_type.rs │ │ │ │ ├── pk_u_8_table.rs │ │ │ │ ├── pk_u_8_type.rs │ │ │ │ ├── pk_uuid_table.rs │ │ │ │ ├── pk_uuid_type.rs │ │ │ │ ├── result_every_primitive_struct_string_table.rs │ │ │ │ ├── result_every_primitive_struct_string_type.rs │ │ │ │ ├── result_i_32_string_table.rs │ │ │ │ ├── result_i_32_string_type.rs │ │ │ │ ├── result_identity_string_table.rs │ │ │ │ ├── result_identity_string_type.rs │ │ │ │ ├── result_simple_enum_i_32_table.rs │ │ │ │ ├── result_simple_enum_i_32_type.rs │ │ │ │ ├── result_string_i_32_table.rs │ │ │ │ ├── result_string_i_32_type.rs │ │ │ │ ├── result_vec_i_32_string_table.rs │ │ │ │ ├── result_vec_i_32_string_type.rs │ │ │ │ ├── scheduled_table_table.rs │ │ │ │ ├── scheduled_table_type.rs │ │ │ │ ├── send_scheduled_message_reducer.rs │ │ │ │ ├── simple_enum_type.rs │ │ │ │ ├── sorted_uuids_insert_reducer.rs │ │ │ │ ├── table_holds_table_table.rs │ │ │ │ ├── table_holds_table_type.rs │ │ │ │ ├── unique_bool_table.rs │ │ │ │ ├── unique_bool_type.rs │ │ │ │ ├── unique_connection_id_table.rs │ │ │ │ ├── unique_connection_id_type.rs │ │ │ │ ├── unique_i_128_table.rs │ │ │ │ ├── unique_i_128_type.rs │ │ │ │ ├── unique_i_16_table.rs │ │ │ │ ├── unique_i_16_type.rs │ │ │ │ ├── unique_i_256_table.rs │ │ │ │ ├── unique_i_256_type.rs │ │ │ │ ├── unique_i_32_table.rs │ │ │ │ ├── unique_i_32_type.rs │ │ │ │ ├── unique_i_64_table.rs │ │ │ │ ├── unique_i_64_type.rs │ │ │ │ ├── unique_i_8_table.rs │ │ │ │ ├── unique_i_8_type.rs │ │ │ │ ├── unique_identity_table.rs │ │ │ │ ├── unique_identity_type.rs │ │ │ │ ├── unique_string_table.rs │ │ │ │ ├── unique_string_type.rs │ │ │ │ ├── unique_u_128_table.rs │ │ │ │ ├── unique_u_128_type.rs │ │ │ │ ├── unique_u_16_table.rs │ │ │ │ ├── unique_u_16_type.rs │ │ │ │ ├── unique_u_256_table.rs │ │ │ │ ├── unique_u_256_type.rs │ │ │ │ ├── unique_u_32_table.rs │ │ │ │ ├── unique_u_32_type.rs │ │ │ │ ├── unique_u_64_table.rs │ │ │ │ ├── unique_u_64_type.rs │ │ │ │ ├── unique_u_8_table.rs │ │ │ │ ├── unique_u_8_type.rs │ │ │ │ ├── unique_uuid_table.rs │ │ │ │ ├── unique_uuid_type.rs │ │ │ │ ├── unit_struct_type.rs │ │ │ │ ├── update_indexed_simple_enum_reducer.rs │ │ │ │ ├── update_pk_bool_reducer.rs │ │ │ │ ├── update_pk_connection_id_reducer.rs │ │ │ │ ├── update_pk_i_128_reducer.rs │ │ │ │ ├── update_pk_i_16_reducer.rs │ │ │ │ ├── update_pk_i_256_reducer.rs │ │ │ │ ├── update_pk_i_32_reducer.rs │ │ │ │ ├── update_pk_i_64_reducer.rs │ │ │ │ ├── update_pk_i_8_reducer.rs │ │ │ │ ├── update_pk_identity_reducer.rs │ │ │ │ ├── update_pk_simple_enum_reducer.rs │ │ │ │ ├── update_pk_string_reducer.rs │ │ │ │ ├── update_pk_u_128_reducer.rs │ │ │ │ ├── update_pk_u_16_reducer.rs │ │ │ │ ├── update_pk_u_256_reducer.rs │ │ │ │ ├── update_pk_u_32_reducer.rs │ │ │ │ ├── update_pk_u_32_two_reducer.rs │ │ │ │ ├── update_pk_u_64_reducer.rs │ │ │ │ ├── update_pk_u_8_reducer.rs │ │ │ │ ├── update_pk_uuid_reducer.rs │ │ │ │ ├── update_unique_bool_reducer.rs │ │ │ │ ├── update_unique_connection_id_reducer.rs │ │ │ │ ├── update_unique_i_128_reducer.rs │ │ │ │ ├── update_unique_i_16_reducer.rs │ │ │ │ ├── update_unique_i_256_reducer.rs │ │ │ │ ├── update_unique_i_32_reducer.rs │ │ │ │ ├── update_unique_i_64_reducer.rs │ │ │ │ ├── update_unique_i_8_reducer.rs │ │ │ │ ├── update_unique_identity_reducer.rs │ │ │ │ ├── update_unique_string_reducer.rs │ │ │ │ ├── update_unique_u_128_reducer.rs │ │ │ │ ├── update_unique_u_16_reducer.rs │ │ │ │ ├── update_unique_u_256_reducer.rs │ │ │ │ ├── update_unique_u_32_reducer.rs │ │ │ │ ├── update_unique_u_64_reducer.rs │ │ │ │ ├── update_unique_u_8_reducer.rs │ │ │ │ ├── update_unique_uuid_reducer.rs │ │ │ │ ├── users_table.rs │ │ │ │ ├── users_type.rs │ │ │ │ ├── vec_bool_table.rs │ │ │ │ ├── vec_bool_type.rs │ │ │ │ ├── vec_byte_struct_table.rs │ │ │ │ ├── vec_byte_struct_type.rs │ │ │ │ ├── vec_connection_id_table.rs │ │ │ │ ├── vec_connection_id_type.rs │ │ │ │ ├── vec_enum_with_payload_table.rs │ │ │ │ ├── vec_enum_with_payload_type.rs │ │ │ │ ├── vec_every_primitive_struct_table.rs │ │ │ │ ├── vec_every_primitive_struct_type.rs │ │ │ │ ├── vec_every_vec_struct_table.rs │ │ │ │ ├── vec_every_vec_struct_type.rs │ │ │ │ ├── vec_f_32_table.rs │ │ │ │ ├── vec_f_32_type.rs │ │ │ │ ├── vec_f_64_table.rs │ │ │ │ ├── vec_f_64_type.rs │ │ │ │ ├── vec_i_128_table.rs │ │ │ │ ├── vec_i_128_type.rs │ │ │ │ ├── vec_i_16_table.rs │ │ │ │ ├── vec_i_16_type.rs │ │ │ │ ├── vec_i_256_table.rs │ │ │ │ ├── vec_i_256_type.rs │ │ │ │ ├── vec_i_32_table.rs │ │ │ │ ├── vec_i_32_type.rs │ │ │ │ ├── vec_i_64_table.rs │ │ │ │ ├── vec_i_64_type.rs │ │ │ │ ├── vec_i_8_table.rs │ │ │ │ ├── vec_i_8_type.rs │ │ │ │ ├── vec_identity_table.rs │ │ │ │ ├── vec_identity_type.rs │ │ │ │ ├── vec_simple_enum_table.rs │ │ │ │ ├── vec_simple_enum_type.rs │ │ │ │ ├── vec_string_table.rs │ │ │ │ ├── vec_string_type.rs │ │ │ │ ├── vec_timestamp_table.rs │ │ │ │ ├── vec_timestamp_type.rs │ │ │ │ ├── vec_u_128_table.rs │ │ │ │ ├── vec_u_128_type.rs │ │ │ │ ├── vec_u_16_table.rs │ │ │ │ ├── vec_u_16_type.rs │ │ │ │ ├── vec_u_256_table.rs │ │ │ │ ├── vec_u_256_type.rs │ │ │ │ ├── vec_u_32_table.rs │ │ │ │ ├── vec_u_32_type.rs │ │ │ │ ├── vec_u_64_table.rs │ │ │ │ ├── vec_u_64_type.rs │ │ │ │ ├── vec_u_8_table.rs │ │ │ │ ├── vec_u_8_type.rs │ │ │ │ ├── vec_unit_struct_table.rs │ │ │ │ ├── vec_unit_struct_type.rs │ │ │ │ ├── vec_uuid_table.rs │ │ │ │ └── vec_uuid_type.rs │ │ │ ├── pk_test_table.rs │ │ │ ├── simple_test_table.rs │ │ │ └── unique_test_table.rs │ │ ├── test-counter/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── test.rs │ │ ├── view-client/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── module_bindings/ │ │ │ ├── delete_player_reducer.rs │ │ │ ├── insert_player_reducer.rs │ │ │ ├── mod.rs │ │ │ ├── move_player_reducer.rs │ │ │ ├── my_player_and_level_table.rs │ │ │ ├── my_player_table.rs │ │ │ ├── nearby_players_table.rs │ │ │ ├── player_and_level_type.rs │ │ │ ├── player_level_table.rs │ │ │ ├── player_level_type.rs │ │ │ ├── player_location_table.rs │ │ │ ├── player_location_type.rs │ │ │ ├── player_table.rs │ │ │ ├── player_type.rs │ │ │ └── players_at_level_0_table.rs │ │ └── view-pk-client/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── main.rs │ │ └── module_bindings/ │ │ ├── all_view_pk_players_table.rs │ │ ├── insert_view_pk_membership_reducer.rs │ │ ├── insert_view_pk_membership_secondary_reducer.rs │ │ ├── insert_view_pk_player_reducer.rs │ │ ├── mod.rs │ │ ├── sender_view_pk_players_a_table.rs │ │ ├── sender_view_pk_players_b_table.rs │ │ ├── update_view_pk_player_reducer.rs │ │ ├── view_pk_membership_secondary_table.rs │ │ ├── view_pk_membership_secondary_type.rs │ │ ├── view_pk_membership_table.rs │ │ ├── view_pk_membership_type.rs │ │ ├── view_pk_player_table.rs │ │ └── view_pk_player_type.rs │ └── unreal/ │ ├── .gitignore │ ├── Cargo.toml │ ├── DEVELOP.md │ ├── README.md │ ├── examples/ │ │ ├── QuickstartChat/ │ │ │ ├── .vsconfig │ │ │ ├── Config/ │ │ │ │ ├── DefaultEditor.ini │ │ │ │ ├── DefaultEngine.ini │ │ │ │ ├── DefaultGame.ini │ │ │ │ └── DefaultInput.ini │ │ │ ├── Content/ │ │ │ │ └── BP/ │ │ │ │ ├── BP_ChatClientActor.uasset │ │ │ │ └── BP_ConnectionTestBP.uasset │ │ │ ├── QuickstartChat.uproject │ │ │ └── Source/ │ │ │ ├── QuickstartChat/ │ │ │ │ ├── Private/ │ │ │ │ │ ├── ChatClientActor.cpp │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── SpacetimeDBClient.g.cpp │ │ │ │ │ │ └── Tables/ │ │ │ │ │ │ ├── MessageTable.g.cpp │ │ │ │ │ │ └── UserTable.g.cpp │ │ │ │ │ └── QuickstartChat.cpp │ │ │ │ ├── Public/ │ │ │ │ │ ├── ChatClientActor.h │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── Optionals/ │ │ │ │ │ │ │ └── QuickstartChatOptionalString.g.h │ │ │ │ │ │ ├── ReducerBase.g.h │ │ │ │ │ │ ├── Reducers/ │ │ │ │ │ │ │ ├── SendMessage.g.h │ │ │ │ │ │ │ └── SetName.g.h │ │ │ │ │ │ ├── SpacetimeDBClient.g.h │ │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ │ ├── MessageTable.g.h │ │ │ │ │ │ │ └── UserTable.g.h │ │ │ │ │ │ └── Types/ │ │ │ │ │ │ ├── MessageType.g.h │ │ │ │ │ │ └── UserType.g.h │ │ │ │ │ └── QuickstartChat.h │ │ │ │ └── QuickstartChat.Build.cs │ │ │ ├── QuickstartChat.Target.cs │ │ │ └── QuickstartChatEditor.Target.cs │ │ └── README.md │ ├── src/ │ │ ├── SpacetimeDbSdk/ │ │ │ ├── Source/ │ │ │ │ └── SpacetimeDbSdk/ │ │ │ │ ├── Private/ │ │ │ │ │ ├── Connection/ │ │ │ │ │ │ ├── Callback.cpp │ │ │ │ │ │ ├── Credentials.cpp │ │ │ │ │ │ ├── DbConnectionBase.cpp │ │ │ │ │ │ ├── DbConnectionBuilder.cpp │ │ │ │ │ │ ├── LogCategory.cpp │ │ │ │ │ │ ├── Subscription.cpp │ │ │ │ │ │ └── Websocket.cpp │ │ │ │ │ ├── SpacetimeDbSdk.cpp │ │ │ │ │ └── Tests/ │ │ │ │ │ └── SpacetimeDBBSATNTestOrg.cpp │ │ │ │ ├── Public/ │ │ │ │ │ ├── BSATN/ │ │ │ │ │ │ ├── Core/ │ │ │ │ │ │ │ ├── DEVELOP.md │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ ├── algebraic_type.h │ │ │ │ │ │ │ ├── bsatn.h │ │ │ │ │ │ │ ├── monostate_traits.h │ │ │ │ │ │ │ ├── primitive_traits.h │ │ │ │ │ │ │ ├── reader.h │ │ │ │ │ │ │ ├── schedule_at.h │ │ │ │ │ │ │ ├── schedule_at_impl.h │ │ │ │ │ │ │ ├── serialization.h │ │ │ │ │ │ │ ├── size_calculator.h │ │ │ │ │ │ │ ├── sum_type.h │ │ │ │ │ │ │ ├── time_duration.h │ │ │ │ │ │ │ ├── timestamp.h │ │ │ │ │ │ │ ├── traits.h │ │ │ │ │ │ │ ├── type_extensions.h │ │ │ │ │ │ │ ├── types.h │ │ │ │ │ │ │ ├── types_impl.h │ │ │ │ │ │ │ └── writer.h │ │ │ │ │ │ ├── FEATURES.md │ │ │ │ │ │ ├── MockCoreMinimal.h │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── UEBSATNHelpers.h │ │ │ │ │ │ ├── UESpacetimeDB.h │ │ │ │ │ │ └── UNREAL_BSATN_ADDITIONS.md │ │ │ │ │ ├── Connection/ │ │ │ │ │ │ ├── Callback.h │ │ │ │ │ │ ├── Credentials.h │ │ │ │ │ │ ├── DbConnectionBase.h │ │ │ │ │ │ ├── DbConnectionBuilder.h │ │ │ │ │ │ ├── LogCategory.h │ │ │ │ │ │ ├── ProcedureFlags.h │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── Subscription.h │ │ │ │ │ │ └── Websocket.h │ │ │ │ │ ├── DBCache/ │ │ │ │ │ │ ├── BTreeUniqueIndex.h │ │ │ │ │ │ ├── ClientCache.h │ │ │ │ │ │ ├── IUniqueIndex.h │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── RowEntry.h │ │ │ │ │ │ ├── TableAppliedDiff.h │ │ │ │ │ │ ├── TableCache.h │ │ │ │ │ │ ├── TableHandle.h │ │ │ │ │ │ ├── UniqueConstraintHandle.h │ │ │ │ │ │ ├── UniqueIndex.h │ │ │ │ │ │ └── WithBsatn.h │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── Optionals/ │ │ │ │ │ │ │ ├── SpacetimeDbSdkOptionalQueryRows.g.h │ │ │ │ │ │ │ └── SpacetimeDbSdkOptionalUInt32.g.h │ │ │ │ │ │ ├── Results/ │ │ │ │ │ │ │ └── SpacetimeDbSdkResultQueryRowsString.g.h │ │ │ │ │ │ └── Types/ │ │ │ │ │ │ ├── BsatnRowListType.g.h │ │ │ │ │ │ ├── CallProcedureType.g.h │ │ │ │ │ │ ├── CallReducerType.g.h │ │ │ │ │ │ ├── ClientMessageType.g.h │ │ │ │ │ │ ├── EventTableRowsType.g.h │ │ │ │ │ │ ├── InitialConnectionType.g.h │ │ │ │ │ │ ├── OneOffQueryResultType.g.h │ │ │ │ │ │ ├── OneOffQueryType.g.h │ │ │ │ │ │ ├── PersistentTableRowsType.g.h │ │ │ │ │ │ ├── ProcedureResultType.g.h │ │ │ │ │ │ ├── ProcedureStatusType.g.h │ │ │ │ │ │ ├── QueryRowsType.g.h │ │ │ │ │ │ ├── QuerySetIdType.g.h │ │ │ │ │ │ ├── QuerySetUpdateType.g.h │ │ │ │ │ │ ├── ReducerOkType.g.h │ │ │ │ │ │ ├── ReducerOutcomeType.g.h │ │ │ │ │ │ ├── ReducerResultType.g.h │ │ │ │ │ │ ├── RowSizeHintType.g.h │ │ │ │ │ │ ├── ServerMessageType.g.h │ │ │ │ │ │ ├── SingleTableRowsType.g.h │ │ │ │ │ │ ├── SubscribeAppliedType.g.h │ │ │ │ │ │ ├── SubscribeType.g.h │ │ │ │ │ │ ├── SubscriptionErrorType.g.h │ │ │ │ │ │ ├── TableUpdateRowsType.g.h │ │ │ │ │ │ ├── TableUpdateType.g.h │ │ │ │ │ │ ├── TransactionUpdateType.g.h │ │ │ │ │ │ ├── UnsubscribeAppliedType.g.h │ │ │ │ │ │ ├── UnsubscribeFlagsType.g.h │ │ │ │ │ │ └── UnsubscribeType.g.h │ │ │ │ │ ├── SpacetimeDbSdk.h │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ └── RemoteTable.h │ │ │ │ │ ├── Tests/ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ └── SpacetimeDBBSATNTestOrg.h │ │ │ │ │ └── Types/ │ │ │ │ │ ├── Builtins.h │ │ │ │ │ ├── LargeIntegers.h │ │ │ │ │ ├── README.md │ │ │ │ │ └── UnitType.h │ │ │ │ └── SpacetimeDbSdk.Build.cs │ │ │ └── SpacetimeDbSdk.uplugin │ │ └── lib.rs │ └── tests/ │ ├── README.md │ ├── TestClient/ │ │ ├── .vsconfig │ │ ├── Config/ │ │ │ ├── DefaultEditor.ini │ │ │ ├── DefaultEditorPerProjectUserSettings.ini │ │ │ ├── DefaultEngine.ini │ │ │ ├── DefaultGame.ini │ │ │ └── DefaultInput.ini │ │ ├── Source/ │ │ │ ├── TestClient/ │ │ │ │ ├── Private/ │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── SpacetimeDBClient.g.cpp │ │ │ │ │ │ └── Tables/ │ │ │ │ │ │ ├── BtreeU32Table.g.cpp │ │ │ │ │ │ ├── IndexedSimpleEnumTable.g.cpp │ │ │ │ │ │ ├── LargeTableTable.g.cpp │ │ │ │ │ │ ├── OneBoolTable.g.cpp │ │ │ │ │ │ ├── OneByteStructTable.g.cpp │ │ │ │ │ │ ├── OneConnectionIdTable.g.cpp │ │ │ │ │ │ ├── OneEnumWithPayloadTable.g.cpp │ │ │ │ │ │ ├── OneEveryPrimitiveStructTable.g.cpp │ │ │ │ │ │ ├── OneEveryVecStructTable.g.cpp │ │ │ │ │ │ ├── OneF32Table.g.cpp │ │ │ │ │ │ ├── OneF64Table.g.cpp │ │ │ │ │ │ ├── OneI128Table.g.cpp │ │ │ │ │ │ ├── OneI16Table.g.cpp │ │ │ │ │ │ ├── OneI256Table.g.cpp │ │ │ │ │ │ ├── OneI32Table.g.cpp │ │ │ │ │ │ ├── OneI64Table.g.cpp │ │ │ │ │ │ ├── OneI8Table.g.cpp │ │ │ │ │ │ ├── OneIdentityTable.g.cpp │ │ │ │ │ │ ├── OneSimpleEnumTable.g.cpp │ │ │ │ │ │ ├── OneStringTable.g.cpp │ │ │ │ │ │ ├── OneTimestampTable.g.cpp │ │ │ │ │ │ ├── OneU128Table.g.cpp │ │ │ │ │ │ ├── OneU16Table.g.cpp │ │ │ │ │ │ ├── OneU256Table.g.cpp │ │ │ │ │ │ ├── OneU32Table.g.cpp │ │ │ │ │ │ ├── OneU64Table.g.cpp │ │ │ │ │ │ ├── OneU8Table.g.cpp │ │ │ │ │ │ ├── OneUnitStructTable.g.cpp │ │ │ │ │ │ ├── OneUuidTable.g.cpp │ │ │ │ │ │ ├── OptionEveryPrimitiveStructTable.g.cpp │ │ │ │ │ │ ├── OptionI32Table.g.cpp │ │ │ │ │ │ ├── OptionIdentityTable.g.cpp │ │ │ │ │ │ ├── OptionSimpleEnumTable.g.cpp │ │ │ │ │ │ ├── OptionStringTable.g.cpp │ │ │ │ │ │ ├── OptionUuidTable.g.cpp │ │ │ │ │ │ ├── OptionVecOptionI32Table.g.cpp │ │ │ │ │ │ ├── PkBoolTable.g.cpp │ │ │ │ │ │ ├── PkConnectionIdTable.g.cpp │ │ │ │ │ │ ├── PkI128Table.g.cpp │ │ │ │ │ │ ├── PkI16Table.g.cpp │ │ │ │ │ │ ├── PkI256Table.g.cpp │ │ │ │ │ │ ├── PkI32Table.g.cpp │ │ │ │ │ │ ├── PkI64Table.g.cpp │ │ │ │ │ │ ├── PkI8Table.g.cpp │ │ │ │ │ │ ├── PkIdentityTable.g.cpp │ │ │ │ │ │ ├── PkSimpleEnumTable.g.cpp │ │ │ │ │ │ ├── PkStringTable.g.cpp │ │ │ │ │ │ ├── PkU128Table.g.cpp │ │ │ │ │ │ ├── PkU16Table.g.cpp │ │ │ │ │ │ ├── PkU256Table.g.cpp │ │ │ │ │ │ ├── PkU32Table.g.cpp │ │ │ │ │ │ ├── PkU32TwoTable.g.cpp │ │ │ │ │ │ ├── PkU64Table.g.cpp │ │ │ │ │ │ ├── PkU8Table.g.cpp │ │ │ │ │ │ ├── PkUuidTable.g.cpp │ │ │ │ │ │ ├── ResultEveryPrimitiveStructStringTable.g.cpp │ │ │ │ │ │ ├── ResultI32StringTable.g.cpp │ │ │ │ │ │ ├── ResultIdentityStringTable.g.cpp │ │ │ │ │ │ ├── ResultSimpleEnumI32Table.g.cpp │ │ │ │ │ │ ├── ResultStringI32Table.g.cpp │ │ │ │ │ │ ├── ResultVecI32StringTable.g.cpp │ │ │ │ │ │ ├── ScheduledTableTable.g.cpp │ │ │ │ │ │ ├── TableHoldsTableTable.g.cpp │ │ │ │ │ │ ├── UniqueBoolTable.g.cpp │ │ │ │ │ │ ├── UniqueConnectionIdTable.g.cpp │ │ │ │ │ │ ├── UniqueI128Table.g.cpp │ │ │ │ │ │ ├── UniqueI16Table.g.cpp │ │ │ │ │ │ ├── UniqueI256Table.g.cpp │ │ │ │ │ │ ├── UniqueI32Table.g.cpp │ │ │ │ │ │ ├── UniqueI64Table.g.cpp │ │ │ │ │ │ ├── UniqueI8Table.g.cpp │ │ │ │ │ │ ├── UniqueIdentityTable.g.cpp │ │ │ │ │ │ ├── UniqueStringTable.g.cpp │ │ │ │ │ │ ├── UniqueU128Table.g.cpp │ │ │ │ │ │ ├── UniqueU16Table.g.cpp │ │ │ │ │ │ ├── UniqueU256Table.g.cpp │ │ │ │ │ │ ├── UniqueU32Table.g.cpp │ │ │ │ │ │ ├── UniqueU64Table.g.cpp │ │ │ │ │ │ ├── UniqueU8Table.g.cpp │ │ │ │ │ │ ├── UniqueUuidTable.g.cpp │ │ │ │ │ │ ├── UsersTable.g.cpp │ │ │ │ │ │ ├── VecBoolTable.g.cpp │ │ │ │ │ │ ├── VecByteStructTable.g.cpp │ │ │ │ │ │ ├── VecConnectionIdTable.g.cpp │ │ │ │ │ │ ├── VecEnumWithPayloadTable.g.cpp │ │ │ │ │ │ ├── VecEveryPrimitiveStructTable.g.cpp │ │ │ │ │ │ ├── VecEveryVecStructTable.g.cpp │ │ │ │ │ │ ├── VecF32Table.g.cpp │ │ │ │ │ │ ├── VecF64Table.g.cpp │ │ │ │ │ │ ├── VecI128Table.g.cpp │ │ │ │ │ │ ├── VecI16Table.g.cpp │ │ │ │ │ │ ├── VecI256Table.g.cpp │ │ │ │ │ │ ├── VecI32Table.g.cpp │ │ │ │ │ │ ├── VecI64Table.g.cpp │ │ │ │ │ │ ├── VecI8Table.g.cpp │ │ │ │ │ │ ├── VecIdentityTable.g.cpp │ │ │ │ │ │ ├── VecSimpleEnumTable.g.cpp │ │ │ │ │ │ ├── VecStringTable.g.cpp │ │ │ │ │ │ ├── VecTimestampTable.g.cpp │ │ │ │ │ │ ├── VecU128Table.g.cpp │ │ │ │ │ │ ├── VecU16Table.g.cpp │ │ │ │ │ │ ├── VecU256Table.g.cpp │ │ │ │ │ │ ├── VecU32Table.g.cpp │ │ │ │ │ │ ├── VecU64Table.g.cpp │ │ │ │ │ │ ├── VecU8Table.g.cpp │ │ │ │ │ │ ├── VecUnitStructTable.g.cpp │ │ │ │ │ │ └── VecUuidTable.g.cpp │ │ │ │ │ └── Tests/ │ │ │ │ │ ├── CommonTestFunctions.cpp │ │ │ │ │ ├── SpacetimeFullClientTests.cpp │ │ │ │ │ ├── TestCounter.cpp │ │ │ │ │ └── TestHandler.cpp │ │ │ │ ├── Public/ │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── Optionals/ │ │ │ │ │ │ │ ├── TestClientOptionalEveryPrimitiveStruct.g.h │ │ │ │ │ │ │ ├── TestClientOptionalIdentity.g.h │ │ │ │ │ │ │ ├── TestClientOptionalInt32.g.h │ │ │ │ │ │ │ ├── TestClientOptionalSimpleEnum.g.h │ │ │ │ │ │ │ ├── TestClientOptionalString.g.h │ │ │ │ │ │ │ ├── TestClientOptionalUuid.g.h │ │ │ │ │ │ │ └── TestClientOptionalVecOptionalInt32.g.h │ │ │ │ │ │ ├── ReducerBase.g.h │ │ │ │ │ │ ├── Reducers/ │ │ │ │ │ │ │ ├── DeleteFromBtreeU32.g.h │ │ │ │ │ │ │ ├── DeleteLargeTable.g.h │ │ │ │ │ │ │ ├── DeletePkBool.g.h │ │ │ │ │ │ │ ├── DeletePkConnectionId.g.h │ │ │ │ │ │ │ ├── DeletePkI128.g.h │ │ │ │ │ │ │ ├── DeletePkI16.g.h │ │ │ │ │ │ │ ├── DeletePkI256.g.h │ │ │ │ │ │ │ ├── DeletePkI32.g.h │ │ │ │ │ │ │ ├── DeletePkI64.g.h │ │ │ │ │ │ │ ├── DeletePkI8.g.h │ │ │ │ │ │ │ ├── DeletePkIdentity.g.h │ │ │ │ │ │ │ ├── DeletePkString.g.h │ │ │ │ │ │ │ ├── DeletePkU128.g.h │ │ │ │ │ │ │ ├── DeletePkU16.g.h │ │ │ │ │ │ │ ├── DeletePkU256.g.h │ │ │ │ │ │ │ ├── DeletePkU32.g.h │ │ │ │ │ │ │ ├── DeletePkU32InsertPkU32Two.g.h │ │ │ │ │ │ │ ├── DeletePkU32Two.g.h │ │ │ │ │ │ │ ├── DeletePkU64.g.h │ │ │ │ │ │ │ ├── DeletePkU8.g.h │ │ │ │ │ │ │ ├── DeletePkUuid.g.h │ │ │ │ │ │ │ ├── DeleteUniqueBool.g.h │ │ │ │ │ │ │ ├── DeleteUniqueConnectionId.g.h │ │ │ │ │ │ │ ├── DeleteUniqueI128.g.h │ │ │ │ │ │ │ ├── DeleteUniqueI16.g.h │ │ │ │ │ │ │ ├── DeleteUniqueI256.g.h │ │ │ │ │ │ │ ├── DeleteUniqueI32.g.h │ │ │ │ │ │ │ ├── DeleteUniqueI64.g.h │ │ │ │ │ │ │ ├── DeleteUniqueI8.g.h │ │ │ │ │ │ │ ├── DeleteUniqueIdentity.g.h │ │ │ │ │ │ │ ├── DeleteUniqueString.g.h │ │ │ │ │ │ │ ├── DeleteUniqueU128.g.h │ │ │ │ │ │ │ ├── DeleteUniqueU16.g.h │ │ │ │ │ │ │ ├── DeleteUniqueU256.g.h │ │ │ │ │ │ │ ├── DeleteUniqueU32.g.h │ │ │ │ │ │ │ ├── DeleteUniqueU64.g.h │ │ │ │ │ │ │ ├── DeleteUniqueU8.g.h │ │ │ │ │ │ │ ├── DeleteUniqueUuid.g.h │ │ │ │ │ │ │ ├── InsertCallTimestamp.g.h │ │ │ │ │ │ │ ├── InsertCallUuidV4.g.h │ │ │ │ │ │ │ ├── InsertCallUuidV7.g.h │ │ │ │ │ │ │ ├── InsertCallerOneConnectionId.g.h │ │ │ │ │ │ │ ├── InsertCallerOneIdentity.g.h │ │ │ │ │ │ │ ├── InsertCallerPkConnectionId.g.h │ │ │ │ │ │ │ ├── InsertCallerPkIdentity.g.h │ │ │ │ │ │ │ ├── InsertCallerUniqueConnectionId.g.h │ │ │ │ │ │ │ ├── InsertCallerUniqueIdentity.g.h │ │ │ │ │ │ │ ├── InsertCallerVecConnectionId.g.h │ │ │ │ │ │ │ ├── InsertCallerVecIdentity.g.h │ │ │ │ │ │ │ ├── InsertIntoBtreeU32.g.h │ │ │ │ │ │ │ ├── InsertIntoIndexedSimpleEnum.g.h │ │ │ │ │ │ │ ├── InsertIntoPkBtreeU32.g.h │ │ │ │ │ │ │ ├── InsertLargeTable.g.h │ │ │ │ │ │ │ ├── InsertOneBool.g.h │ │ │ │ │ │ │ ├── InsertOneByteStruct.g.h │ │ │ │ │ │ │ ├── InsertOneConnectionId.g.h │ │ │ │ │ │ │ ├── InsertOneEnumWithPayload.g.h │ │ │ │ │ │ │ ├── InsertOneEveryPrimitiveStruct.g.h │ │ │ │ │ │ │ ├── InsertOneEveryVecStruct.g.h │ │ │ │ │ │ │ ├── InsertOneF32.g.h │ │ │ │ │ │ │ ├── InsertOneF64.g.h │ │ │ │ │ │ │ ├── InsertOneI128.g.h │ │ │ │ │ │ │ ├── InsertOneI16.g.h │ │ │ │ │ │ │ ├── InsertOneI256.g.h │ │ │ │ │ │ │ ├── InsertOneI32.g.h │ │ │ │ │ │ │ ├── InsertOneI64.g.h │ │ │ │ │ │ │ ├── InsertOneI8.g.h │ │ │ │ │ │ │ ├── InsertOneIdentity.g.h │ │ │ │ │ │ │ ├── InsertOneSimpleEnum.g.h │ │ │ │ │ │ │ ├── InsertOneString.g.h │ │ │ │ │ │ │ ├── InsertOneTimestamp.g.h │ │ │ │ │ │ │ ├── InsertOneU128.g.h │ │ │ │ │ │ │ ├── InsertOneU16.g.h │ │ │ │ │ │ │ ├── InsertOneU256.g.h │ │ │ │ │ │ │ ├── InsertOneU32.g.h │ │ │ │ │ │ │ ├── InsertOneU64.g.h │ │ │ │ │ │ │ ├── InsertOneU8.g.h │ │ │ │ │ │ │ ├── InsertOneUnitStruct.g.h │ │ │ │ │ │ │ ├── InsertOneUuid.g.h │ │ │ │ │ │ │ ├── InsertOptionEveryPrimitiveStruct.g.h │ │ │ │ │ │ │ ├── InsertOptionI32.g.h │ │ │ │ │ │ │ ├── InsertOptionIdentity.g.h │ │ │ │ │ │ │ ├── InsertOptionSimpleEnum.g.h │ │ │ │ │ │ │ ├── InsertOptionString.g.h │ │ │ │ │ │ │ ├── InsertOptionUuid.g.h │ │ │ │ │ │ │ ├── InsertOptionVecOptionI32.g.h │ │ │ │ │ │ │ ├── InsertPkBool.g.h │ │ │ │ │ │ │ ├── InsertPkConnectionId.g.h │ │ │ │ │ │ │ ├── InsertPkI128.g.h │ │ │ │ │ │ │ ├── InsertPkI16.g.h │ │ │ │ │ │ │ ├── InsertPkI256.g.h │ │ │ │ │ │ │ ├── InsertPkI32.g.h │ │ │ │ │ │ │ ├── InsertPkI64.g.h │ │ │ │ │ │ │ ├── InsertPkI8.g.h │ │ │ │ │ │ │ ├── InsertPkIdentity.g.h │ │ │ │ │ │ │ ├── InsertPkSimpleEnum.g.h │ │ │ │ │ │ │ ├── InsertPkString.g.h │ │ │ │ │ │ │ ├── InsertPkU128.g.h │ │ │ │ │ │ │ ├── InsertPkU16.g.h │ │ │ │ │ │ │ ├── InsertPkU256.g.h │ │ │ │ │ │ │ ├── InsertPkU32.g.h │ │ │ │ │ │ │ ├── InsertPkU32Two.g.h │ │ │ │ │ │ │ ├── InsertPkU64.g.h │ │ │ │ │ │ │ ├── InsertPkU8.g.h │ │ │ │ │ │ │ ├── InsertPkUuid.g.h │ │ │ │ │ │ │ ├── InsertPrimitivesAsStrings.g.h │ │ │ │ │ │ │ ├── InsertResultEveryPrimitiveStructString.g.h │ │ │ │ │ │ │ ├── InsertResultI32String.g.h │ │ │ │ │ │ │ ├── InsertResultIdentityString.g.h │ │ │ │ │ │ │ ├── InsertResultSimpleEnumI32.g.h │ │ │ │ │ │ │ ├── InsertResultStringI32.g.h │ │ │ │ │ │ │ ├── InsertResultVecI32String.g.h │ │ │ │ │ │ │ ├── InsertTableHoldsTable.g.h │ │ │ │ │ │ │ ├── InsertUniqueBool.g.h │ │ │ │ │ │ │ ├── InsertUniqueConnectionId.g.h │ │ │ │ │ │ │ ├── InsertUniqueI128.g.h │ │ │ │ │ │ │ ├── InsertUniqueI16.g.h │ │ │ │ │ │ │ ├── InsertUniqueI256.g.h │ │ │ │ │ │ │ ├── InsertUniqueI32.g.h │ │ │ │ │ │ │ ├── InsertUniqueI64.g.h │ │ │ │ │ │ │ ├── InsertUniqueI8.g.h │ │ │ │ │ │ │ ├── InsertUniqueIdentity.g.h │ │ │ │ │ │ │ ├── InsertUniqueString.g.h │ │ │ │ │ │ │ ├── InsertUniqueU128.g.h │ │ │ │ │ │ │ ├── InsertUniqueU16.g.h │ │ │ │ │ │ │ ├── InsertUniqueU256.g.h │ │ │ │ │ │ │ ├── InsertUniqueU32.g.h │ │ │ │ │ │ │ ├── InsertUniqueU32UpdatePkU32.g.h │ │ │ │ │ │ │ ├── InsertUniqueU64.g.h │ │ │ │ │ │ │ ├── InsertUniqueU8.g.h │ │ │ │ │ │ │ ├── InsertUniqueUuid.g.h │ │ │ │ │ │ │ ├── InsertUser.g.h │ │ │ │ │ │ │ ├── InsertVecBool.g.h │ │ │ │ │ │ │ ├── InsertVecByteStruct.g.h │ │ │ │ │ │ │ ├── InsertVecConnectionId.g.h │ │ │ │ │ │ │ ├── InsertVecEnumWithPayload.g.h │ │ │ │ │ │ │ ├── InsertVecEveryPrimitiveStruct.g.h │ │ │ │ │ │ │ ├── InsertVecEveryVecStruct.g.h │ │ │ │ │ │ │ ├── InsertVecF32.g.h │ │ │ │ │ │ │ ├── InsertVecF64.g.h │ │ │ │ │ │ │ ├── InsertVecI128.g.h │ │ │ │ │ │ │ ├── InsertVecI16.g.h │ │ │ │ │ │ │ ├── InsertVecI256.g.h │ │ │ │ │ │ │ ├── InsertVecI32.g.h │ │ │ │ │ │ │ ├── InsertVecI64.g.h │ │ │ │ │ │ │ ├── InsertVecI8.g.h │ │ │ │ │ │ │ ├── InsertVecIdentity.g.h │ │ │ │ │ │ │ ├── InsertVecSimpleEnum.g.h │ │ │ │ │ │ │ ├── InsertVecString.g.h │ │ │ │ │ │ │ ├── InsertVecTimestamp.g.h │ │ │ │ │ │ │ ├── InsertVecU128.g.h │ │ │ │ │ │ │ ├── InsertVecU16.g.h │ │ │ │ │ │ │ ├── InsertVecU256.g.h │ │ │ │ │ │ │ ├── InsertVecU32.g.h │ │ │ │ │ │ │ ├── InsertVecU64.g.h │ │ │ │ │ │ │ ├── InsertVecU8.g.h │ │ │ │ │ │ │ ├── InsertVecUnitStruct.g.h │ │ │ │ │ │ │ ├── InsertVecUuid.g.h │ │ │ │ │ │ │ ├── NoOpSucceeds.g.h │ │ │ │ │ │ │ ├── SortedUuidsInsert.g.h │ │ │ │ │ │ │ ├── UpdateIndexedSimpleEnum.g.h │ │ │ │ │ │ │ ├── UpdatePkBool.g.h │ │ │ │ │ │ │ ├── UpdatePkConnectionId.g.h │ │ │ │ │ │ │ ├── UpdatePkI128.g.h │ │ │ │ │ │ │ ├── UpdatePkI16.g.h │ │ │ │ │ │ │ ├── UpdatePkI256.g.h │ │ │ │ │ │ │ ├── UpdatePkI32.g.h │ │ │ │ │ │ │ ├── UpdatePkI64.g.h │ │ │ │ │ │ │ ├── UpdatePkI8.g.h │ │ │ │ │ │ │ ├── UpdatePkIdentity.g.h │ │ │ │ │ │ │ ├── UpdatePkSimpleEnum.g.h │ │ │ │ │ │ │ ├── UpdatePkString.g.h │ │ │ │ │ │ │ ├── UpdatePkU128.g.h │ │ │ │ │ │ │ ├── UpdatePkU16.g.h │ │ │ │ │ │ │ ├── UpdatePkU256.g.h │ │ │ │ │ │ │ ├── UpdatePkU32.g.h │ │ │ │ │ │ │ ├── UpdatePkU32Two.g.h │ │ │ │ │ │ │ ├── UpdatePkU64.g.h │ │ │ │ │ │ │ ├── UpdatePkU8.g.h │ │ │ │ │ │ │ ├── UpdatePkUuid.g.h │ │ │ │ │ │ │ ├── UpdateUniqueBool.g.h │ │ │ │ │ │ │ ├── UpdateUniqueConnectionId.g.h │ │ │ │ │ │ │ ├── UpdateUniqueI128.g.h │ │ │ │ │ │ │ ├── UpdateUniqueI16.g.h │ │ │ │ │ │ │ ├── UpdateUniqueI256.g.h │ │ │ │ │ │ │ ├── UpdateUniqueI32.g.h │ │ │ │ │ │ │ ├── UpdateUniqueI64.g.h │ │ │ │ │ │ │ ├── UpdateUniqueI8.g.h │ │ │ │ │ │ │ ├── UpdateUniqueIdentity.g.h │ │ │ │ │ │ │ ├── UpdateUniqueString.g.h │ │ │ │ │ │ │ ├── UpdateUniqueU128.g.h │ │ │ │ │ │ │ ├── UpdateUniqueU16.g.h │ │ │ │ │ │ │ ├── UpdateUniqueU256.g.h │ │ │ │ │ │ │ ├── UpdateUniqueU32.g.h │ │ │ │ │ │ │ ├── UpdateUniqueU64.g.h │ │ │ │ │ │ │ ├── UpdateUniqueU8.g.h │ │ │ │ │ │ │ └── UpdateUniqueUuid.g.h │ │ │ │ │ │ ├── Results/ │ │ │ │ │ │ │ ├── TestClientResultEveryPrimitiveStructString.g.h │ │ │ │ │ │ │ ├── TestClientResultIdentityString.g.h │ │ │ │ │ │ │ ├── TestClientResultInt32String.g.h │ │ │ │ │ │ │ ├── TestClientResultSimpleEnumInt32.g.h │ │ │ │ │ │ │ ├── TestClientResultStringInt32.g.h │ │ │ │ │ │ │ └── TestClientResultVecInt32String.g.h │ │ │ │ │ │ ├── SpacetimeDBClient.g.h │ │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ │ ├── BtreeU32Table.g.h │ │ │ │ │ │ │ ├── IndexedSimpleEnumTable.g.h │ │ │ │ │ │ │ ├── LargeTableTable.g.h │ │ │ │ │ │ │ ├── OneBoolTable.g.h │ │ │ │ │ │ │ ├── OneByteStructTable.g.h │ │ │ │ │ │ │ ├── OneConnectionIdTable.g.h │ │ │ │ │ │ │ ├── OneEnumWithPayloadTable.g.h │ │ │ │ │ │ │ ├── OneEveryPrimitiveStructTable.g.h │ │ │ │ │ │ │ ├── OneEveryVecStructTable.g.h │ │ │ │ │ │ │ ├── OneF32Table.g.h │ │ │ │ │ │ │ ├── OneF64Table.g.h │ │ │ │ │ │ │ ├── OneI128Table.g.h │ │ │ │ │ │ │ ├── OneI16Table.g.h │ │ │ │ │ │ │ ├── OneI256Table.g.h │ │ │ │ │ │ │ ├── OneI32Table.g.h │ │ │ │ │ │ │ ├── OneI64Table.g.h │ │ │ │ │ │ │ ├── OneI8Table.g.h │ │ │ │ │ │ │ ├── OneIdentityTable.g.h │ │ │ │ │ │ │ ├── OneSimpleEnumTable.g.h │ │ │ │ │ │ │ ├── OneStringTable.g.h │ │ │ │ │ │ │ ├── OneTimestampTable.g.h │ │ │ │ │ │ │ ├── OneU128Table.g.h │ │ │ │ │ │ │ ├── OneU16Table.g.h │ │ │ │ │ │ │ ├── OneU256Table.g.h │ │ │ │ │ │ │ ├── OneU32Table.g.h │ │ │ │ │ │ │ ├── OneU64Table.g.h │ │ │ │ │ │ │ ├── OneU8Table.g.h │ │ │ │ │ │ │ ├── OneUnitStructTable.g.h │ │ │ │ │ │ │ ├── OneUuidTable.g.h │ │ │ │ │ │ │ ├── OptionEveryPrimitiveStructTable.g.h │ │ │ │ │ │ │ ├── OptionI32Table.g.h │ │ │ │ │ │ │ ├── OptionIdentityTable.g.h │ │ │ │ │ │ │ ├── OptionSimpleEnumTable.g.h │ │ │ │ │ │ │ ├── OptionStringTable.g.h │ │ │ │ │ │ │ ├── OptionUuidTable.g.h │ │ │ │ │ │ │ ├── OptionVecOptionI32Table.g.h │ │ │ │ │ │ │ ├── PkBoolTable.g.h │ │ │ │ │ │ │ ├── PkConnectionIdTable.g.h │ │ │ │ │ │ │ ├── PkI128Table.g.h │ │ │ │ │ │ │ ├── PkI16Table.g.h │ │ │ │ │ │ │ ├── PkI256Table.g.h │ │ │ │ │ │ │ ├── PkI32Table.g.h │ │ │ │ │ │ │ ├── PkI64Table.g.h │ │ │ │ │ │ │ ├── PkI8Table.g.h │ │ │ │ │ │ │ ├── PkIdentityTable.g.h │ │ │ │ │ │ │ ├── PkSimpleEnumTable.g.h │ │ │ │ │ │ │ ├── PkStringTable.g.h │ │ │ │ │ │ │ ├── PkU128Table.g.h │ │ │ │ │ │ │ ├── PkU16Table.g.h │ │ │ │ │ │ │ ├── PkU256Table.g.h │ │ │ │ │ │ │ ├── PkU32Table.g.h │ │ │ │ │ │ │ ├── PkU32TwoTable.g.h │ │ │ │ │ │ │ ├── PkU64Table.g.h │ │ │ │ │ │ │ ├── PkU8Table.g.h │ │ │ │ │ │ │ ├── PkUuidTable.g.h │ │ │ │ │ │ │ ├── ResultEveryPrimitiveStructStringTable.g.h │ │ │ │ │ │ │ ├── ResultI32StringTable.g.h │ │ │ │ │ │ │ ├── ResultIdentityStringTable.g.h │ │ │ │ │ │ │ ├── ResultSimpleEnumI32Table.g.h │ │ │ │ │ │ │ ├── ResultStringI32Table.g.h │ │ │ │ │ │ │ ├── ResultVecI32StringTable.g.h │ │ │ │ │ │ │ ├── ScheduledTableTable.g.h │ │ │ │ │ │ │ ├── TableHoldsTableTable.g.h │ │ │ │ │ │ │ ├── UniqueBoolTable.g.h │ │ │ │ │ │ │ ├── UniqueConnectionIdTable.g.h │ │ │ │ │ │ │ ├── UniqueI128Table.g.h │ │ │ │ │ │ │ ├── UniqueI16Table.g.h │ │ │ │ │ │ │ ├── UniqueI256Table.g.h │ │ │ │ │ │ │ ├── UniqueI32Table.g.h │ │ │ │ │ │ │ ├── UniqueI64Table.g.h │ │ │ │ │ │ │ ├── UniqueI8Table.g.h │ │ │ │ │ │ │ ├── UniqueIdentityTable.g.h │ │ │ │ │ │ │ ├── UniqueStringTable.g.h │ │ │ │ │ │ │ ├── UniqueU128Table.g.h │ │ │ │ │ │ │ ├── UniqueU16Table.g.h │ │ │ │ │ │ │ ├── UniqueU256Table.g.h │ │ │ │ │ │ │ ├── UniqueU32Table.g.h │ │ │ │ │ │ │ ├── UniqueU64Table.g.h │ │ │ │ │ │ │ ├── UniqueU8Table.g.h │ │ │ │ │ │ │ ├── UniqueUuidTable.g.h │ │ │ │ │ │ │ ├── UsersTable.g.h │ │ │ │ │ │ │ ├── VecBoolTable.g.h │ │ │ │ │ │ │ ├── VecByteStructTable.g.h │ │ │ │ │ │ │ ├── VecConnectionIdTable.g.h │ │ │ │ │ │ │ ├── VecEnumWithPayloadTable.g.h │ │ │ │ │ │ │ ├── VecEveryPrimitiveStructTable.g.h │ │ │ │ │ │ │ ├── VecEveryVecStructTable.g.h │ │ │ │ │ │ │ ├── VecF32Table.g.h │ │ │ │ │ │ │ ├── VecF64Table.g.h │ │ │ │ │ │ │ ├── VecI128Table.g.h │ │ │ │ │ │ │ ├── VecI16Table.g.h │ │ │ │ │ │ │ ├── VecI256Table.g.h │ │ │ │ │ │ │ ├── VecI32Table.g.h │ │ │ │ │ │ │ ├── VecI64Table.g.h │ │ │ │ │ │ │ ├── VecI8Table.g.h │ │ │ │ │ │ │ ├── VecIdentityTable.g.h │ │ │ │ │ │ │ ├── VecSimpleEnumTable.g.h │ │ │ │ │ │ │ ├── VecStringTable.g.h │ │ │ │ │ │ │ ├── VecTimestampTable.g.h │ │ │ │ │ │ │ ├── VecU128Table.g.h │ │ │ │ │ │ │ ├── VecU16Table.g.h │ │ │ │ │ │ │ ├── VecU256Table.g.h │ │ │ │ │ │ │ ├── VecU32Table.g.h │ │ │ │ │ │ │ ├── VecU64Table.g.h │ │ │ │ │ │ │ ├── VecU8Table.g.h │ │ │ │ │ │ │ ├── VecUnitStructTable.g.h │ │ │ │ │ │ │ └── VecUuidTable.g.h │ │ │ │ │ │ └── Types/ │ │ │ │ │ │ ├── BTreeU32Type.g.h │ │ │ │ │ │ ├── ByteStructType.g.h │ │ │ │ │ │ ├── EnumWithPayloadType.g.h │ │ │ │ │ │ ├── EveryPrimitiveStructType.g.h │ │ │ │ │ │ ├── EveryVecStructType.g.h │ │ │ │ │ │ ├── IndexedSimpleEnumType.g.h │ │ │ │ │ │ ├── IndexedTable2Type.g.h │ │ │ │ │ │ ├── IndexedTableType.g.h │ │ │ │ │ │ ├── LargeTableType.g.h │ │ │ │ │ │ ├── OneBoolType.g.h │ │ │ │ │ │ ├── OneByteStructType.g.h │ │ │ │ │ │ ├── OneConnectionIdType.g.h │ │ │ │ │ │ ├── OneEnumWithPayloadType.g.h │ │ │ │ │ │ ├── OneEveryPrimitiveStructType.g.h │ │ │ │ │ │ ├── OneEveryVecStructType.g.h │ │ │ │ │ │ ├── OneF32Type.g.h │ │ │ │ │ │ ├── OneF64Type.g.h │ │ │ │ │ │ ├── OneI128Type.g.h │ │ │ │ │ │ ├── OneI16Type.g.h │ │ │ │ │ │ ├── OneI256Type.g.h │ │ │ │ │ │ ├── OneI32Type.g.h │ │ │ │ │ │ ├── OneI64Type.g.h │ │ │ │ │ │ ├── OneI8Type.g.h │ │ │ │ │ │ ├── OneIdentityType.g.h │ │ │ │ │ │ ├── OneSimpleEnumType.g.h │ │ │ │ │ │ ├── OneStringType.g.h │ │ │ │ │ │ ├── OneTimestampType.g.h │ │ │ │ │ │ ├── OneU128Type.g.h │ │ │ │ │ │ ├── OneU16Type.g.h │ │ │ │ │ │ ├── OneU256Type.g.h │ │ │ │ │ │ ├── OneU32Type.g.h │ │ │ │ │ │ ├── OneU64Type.g.h │ │ │ │ │ │ ├── OneU8Type.g.h │ │ │ │ │ │ ├── OneUnitStructType.g.h │ │ │ │ │ │ ├── OneUuidType.g.h │ │ │ │ │ │ ├── OptionEveryPrimitiveStructType.g.h │ │ │ │ │ │ ├── OptionI32Type.g.h │ │ │ │ │ │ ├── OptionIdentityType.g.h │ │ │ │ │ │ ├── OptionSimpleEnumType.g.h │ │ │ │ │ │ ├── OptionStringType.g.h │ │ │ │ │ │ ├── OptionUuidType.g.h │ │ │ │ │ │ ├── OptionVecOptionI32Type.g.h │ │ │ │ │ │ ├── PkBoolType.g.h │ │ │ │ │ │ ├── PkConnectionIdType.g.h │ │ │ │ │ │ ├── PkI128Type.g.h │ │ │ │ │ │ ├── PkI16Type.g.h │ │ │ │ │ │ ├── PkI256Type.g.h │ │ │ │ │ │ ├── PkI32Type.g.h │ │ │ │ │ │ ├── PkI64Type.g.h │ │ │ │ │ │ ├── PkI8Type.g.h │ │ │ │ │ │ ├── PkIdentityType.g.h │ │ │ │ │ │ ├── PkSimpleEnumType.g.h │ │ │ │ │ │ ├── PkStringType.g.h │ │ │ │ │ │ ├── PkU128Type.g.h │ │ │ │ │ │ ├── PkU16Type.g.h │ │ │ │ │ │ ├── PkU256Type.g.h │ │ │ │ │ │ ├── PkU32TwoType.g.h │ │ │ │ │ │ ├── PkU32Type.g.h │ │ │ │ │ │ ├── PkU64Type.g.h │ │ │ │ │ │ ├── PkU8Type.g.h │ │ │ │ │ │ ├── PkUuidType.g.h │ │ │ │ │ │ ├── ResultEveryPrimitiveStructStringType.g.h │ │ │ │ │ │ ├── ResultI32StringType.g.h │ │ │ │ │ │ ├── ResultIdentityStringType.g.h │ │ │ │ │ │ ├── ResultSimpleEnumI32Type.g.h │ │ │ │ │ │ ├── ResultStringI32Type.g.h │ │ │ │ │ │ ├── ResultVecI32StringType.g.h │ │ │ │ │ │ ├── ScheduledTableType.g.h │ │ │ │ │ │ ├── SimpleEnumType.g.h │ │ │ │ │ │ ├── TableHoldsTableType.g.h │ │ │ │ │ │ ├── UniqueBoolType.g.h │ │ │ │ │ │ ├── UniqueConnectionIdType.g.h │ │ │ │ │ │ ├── UniqueI128Type.g.h │ │ │ │ │ │ ├── UniqueI16Type.g.h │ │ │ │ │ │ ├── UniqueI256Type.g.h │ │ │ │ │ │ ├── UniqueI32Type.g.h │ │ │ │ │ │ ├── UniqueI64Type.g.h │ │ │ │ │ │ ├── UniqueI8Type.g.h │ │ │ │ │ │ ├── UniqueIdentityType.g.h │ │ │ │ │ │ ├── UniqueStringType.g.h │ │ │ │ │ │ ├── UniqueU128Type.g.h │ │ │ │ │ │ ├── UniqueU16Type.g.h │ │ │ │ │ │ ├── UniqueU256Type.g.h │ │ │ │ │ │ ├── UniqueU32Type.g.h │ │ │ │ │ │ ├── UniqueU64Type.g.h │ │ │ │ │ │ ├── UniqueU8Type.g.h │ │ │ │ │ │ ├── UniqueUuidType.g.h │ │ │ │ │ │ ├── UnitStructType.g.h │ │ │ │ │ │ ├── UsersType.g.h │ │ │ │ │ │ ├── VecBoolType.g.h │ │ │ │ │ │ ├── VecByteStructType.g.h │ │ │ │ │ │ ├── VecConnectionIdType.g.h │ │ │ │ │ │ ├── VecEnumWithPayloadType.g.h │ │ │ │ │ │ ├── VecEveryPrimitiveStructType.g.h │ │ │ │ │ │ ├── VecEveryVecStructType.g.h │ │ │ │ │ │ ├── VecF32Type.g.h │ │ │ │ │ │ ├── VecF64Type.g.h │ │ │ │ │ │ ├── VecI128Type.g.h │ │ │ │ │ │ ├── VecI16Type.g.h │ │ │ │ │ │ ├── VecI256Type.g.h │ │ │ │ │ │ ├── VecI32Type.g.h │ │ │ │ │ │ ├── VecI64Type.g.h │ │ │ │ │ │ ├── VecI8Type.g.h │ │ │ │ │ │ ├── VecIdentityType.g.h │ │ │ │ │ │ ├── VecSimpleEnumType.g.h │ │ │ │ │ │ ├── VecStringType.g.h │ │ │ │ │ │ ├── VecTimestampType.g.h │ │ │ │ │ │ ├── VecU128Type.g.h │ │ │ │ │ │ ├── VecU16Type.g.h │ │ │ │ │ │ ├── VecU256Type.g.h │ │ │ │ │ │ ├── VecU32Type.g.h │ │ │ │ │ │ ├── VecU64Type.g.h │ │ │ │ │ │ ├── VecU8Type.g.h │ │ │ │ │ │ ├── VecUnitStructType.g.h │ │ │ │ │ │ └── VecUuidType.g.h │ │ │ │ │ └── Tests/ │ │ │ │ │ ├── CommonTestFunctions.h │ │ │ │ │ ├── PrimitiveHandlerList.def │ │ │ │ │ ├── SpacetimeFullClientTests.h │ │ │ │ │ ├── TestCounter.h │ │ │ │ │ ├── TestHandler.h │ │ │ │ │ ├── UmbreallaHeaderReducers.h │ │ │ │ │ ├── UmbreallaHeaderTypes.h │ │ │ │ │ └── UmbreallaHeaderaTables.h │ │ │ │ ├── TestClient.Build.cs │ │ │ │ ├── TestClient.cpp │ │ │ │ ├── TestClient.h │ │ │ │ ├── TestClientGameModeBase.cpp │ │ │ │ └── TestClientGameModeBase.h │ │ │ ├── TestClient.Target.cs │ │ │ └── TestClientEditor.Target.cs │ │ └── TestClient.uproject │ ├── TestProcClient/ │ │ ├── .vsconfig │ │ ├── Config/ │ │ │ ├── DefaultEditor.ini │ │ │ ├── DefaultEditorPerProjectUserSettings.ini │ │ │ ├── DefaultEngine.ini │ │ │ ├── DefaultGame.ini │ │ │ └── DefaultInput.ini │ │ ├── Source/ │ │ │ ├── TestProcClient/ │ │ │ │ ├── Private/ │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── SpacetimeDBClient.g.cpp │ │ │ │ │ │ └── Tables/ │ │ │ │ │ │ ├── MyTableTable.g.cpp │ │ │ │ │ │ ├── PkUuidTable.g.cpp │ │ │ │ │ │ └── ProcInsertsIntoTable.g.cpp │ │ │ │ │ └── Tests/ │ │ │ │ │ ├── CommonTestFunctions.cpp │ │ │ │ │ ├── SpacetimeFullClientTests.cpp │ │ │ │ │ ├── TestCounter.cpp │ │ │ │ │ └── TestHandler.cpp │ │ │ │ ├── Public/ │ │ │ │ │ ├── ModuleBindings/ │ │ │ │ │ │ ├── Procedures/ │ │ │ │ │ │ │ ├── InsertWithTxCommit.g.h │ │ │ │ │ │ │ ├── InsertWithTxRollback.g.h │ │ │ │ │ │ │ ├── InvalidRequest.g.h │ │ │ │ │ │ │ ├── ReadMySchema.g.h │ │ │ │ │ │ │ ├── ReturnEnumA.g.h │ │ │ │ │ │ │ ├── ReturnEnumB.g.h │ │ │ │ │ │ │ ├── ReturnPrimitive.g.h │ │ │ │ │ │ │ ├── ReturnStruct.g.h │ │ │ │ │ │ │ ├── SortedUuidsInsert.g.h │ │ │ │ │ │ │ └── WillPanic.g.h │ │ │ │ │ │ ├── ReducerBase.g.h │ │ │ │ │ │ ├── Reducers/ │ │ │ │ │ │ │ └── ScheduleProc.g.h │ │ │ │ │ │ ├── SpacetimeDBClient.g.h │ │ │ │ │ │ ├── Tables/ │ │ │ │ │ │ │ ├── MyTableTable.g.h │ │ │ │ │ │ │ ├── PkUuidTable.g.h │ │ │ │ │ │ │ └── ProcInsertsIntoTable.g.h │ │ │ │ │ │ └── Types/ │ │ │ │ │ │ ├── MyTableType.g.h │ │ │ │ │ │ ├── PkUuidType.g.h │ │ │ │ │ │ ├── ProcInsertsIntoType.g.h │ │ │ │ │ │ ├── ReturnEnumType.g.h │ │ │ │ │ │ ├── ReturnStructType.g.h │ │ │ │ │ │ └── ScheduledProcTableType.g.h │ │ │ │ │ └── Tests/ │ │ │ │ │ ├── CommonTestFunctions.h │ │ │ │ │ ├── SpacetimeFullClientTests.h │ │ │ │ │ ├── TestCounter.h │ │ │ │ │ ├── TestHandler.h │ │ │ │ │ ├── UmbreallaHeaderProcedures.h │ │ │ │ │ └── UmbreallaHeaderTypes.h │ │ │ │ ├── TestProcClient.Build.cs │ │ │ │ ├── TestProcClient.cpp │ │ │ │ ├── TestProcClient.h │ │ │ │ ├── TestProcClientGameModeBase.cpp │ │ │ │ └── TestProcClientGameModeBase.h │ │ │ ├── TestProcClient.Target.cs │ │ │ └── TestProcClientEditor.Target.cs │ │ └── TestProcClient.uproject │ ├── sdk_unreal_harness.rs │ ├── test.rs │ └── test_procedure.rs ├── skills/ │ ├── spacetimedb-cli/ │ │ └── SKILL.md │ ├── spacetimedb-concepts/ │ │ └── SKILL.md │ ├── spacetimedb-csharp/ │ │ └── SKILL.md │ ├── spacetimedb-rust/ │ │ └── SKILL.md │ ├── spacetimedb-typescript/ │ │ └── SKILL.md │ └── spacetimedb-unity/ │ └── SKILL.md ├── smoketests/ │ ├── README.md │ ├── __init__.py │ ├── __main__.py │ ├── config.toml │ ├── docker.py │ ├── requirements.txt │ ├── tests/ │ │ ├── __init__.py │ │ ├── add_remove_index.py │ │ ├── auto_inc.py │ │ ├── auto_migration.py │ │ ├── call.py │ │ ├── clear_database.py │ │ ├── client_connected_error_rejects_connection.py │ │ ├── confirmed_reads.py │ │ ├── connect_disconnect_from_cli.py │ │ ├── create_project.py │ │ ├── csharp_module.py │ │ ├── default_module_clippy.py │ │ ├── delete_database.py │ │ ├── describe.py │ │ ├── detect_wasm_bindgen.py │ │ ├── dml.py │ │ ├── domains.py │ │ ├── fail_initial_publish.py │ │ ├── filtering.py │ │ ├── module_nested_op.py │ │ ├── modules.py │ │ ├── namespaces.py │ │ ├── new_user_flow.py │ │ ├── panic.py │ │ ├── permissions.py │ │ ├── quickstart.py │ │ ├── replication.py │ │ ├── rls.py │ │ ├── schedule_reducer.py │ │ ├── servers.py │ │ ├── sql.py │ │ ├── teams.py │ │ ├── timestamp_route.py │ │ ├── views.py │ │ └── zz_docker.py │ └── unittest_parallel.py ├── templates/ │ ├── angular-ts/ │ │ ├── .gitignore │ │ ├── .template.json │ │ ├── angular.json │ │ ├── package.json │ │ ├── scripts/ │ │ │ └── dev.mjs │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.component.ts │ │ │ │ └── app.config.ts │ │ │ ├── environments/ │ │ │ │ └── environment.ts │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── module_bindings/ │ │ │ │ ├── add_reducer.ts │ │ │ │ ├── index.ts │ │ │ │ ├── person_table.ts │ │ │ │ ├── say_hello_reducer.ts │ │ │ │ ├── types/ │ │ │ │ │ ├── procedures.ts │ │ │ │ │ └── reducers.ts │ │ │ │ └── types.ts │ │ │ └── styles.css │ │ ├── tsconfig.app.json │ │ └── tsconfig.json │ ├── basic-cpp/ │ │ ├── .template.json │ │ ├── Cargo.toml │ │ ├── spacetimedb/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ └── src/ │ │ │ └── lib.cpp │ │ └── src/ │ │ ├── main.rs │ │ └── module_bindings/ │ │ ├── add_reducer.rs │ │ ├── mod.rs │ │ ├── person_table.rs │ │ ├── person_type.rs │ │ └── say_hello_reducer.rs │ ├── basic-cs/ │ │ ├── .template.json │ │ ├── Program.cs │ │ ├── client.csproj │ │ ├── module_bindings/ │ │ │ ├── Reducers/ │ │ │ │ ├── Add.g.cs │ │ │ │ └── SayHello.g.cs │ │ │ ├── SpacetimeDBClient.g.cs │ │ │ ├── Tables/ │ │ │ │ └── Person.g.cs │ │ │ └── Types/ │ │ │ └── Person.g.cs │ │ └── spacetimedb/ │ │ ├── Lib.cs │ │ ├── StdbModule.csproj │ │ └── global.json │ ├── basic-rs/ │ │ ├── .template.json │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── spacetimedb/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── src/ │ │ ├── main.rs │ │ └── module_bindings/ │ │ ├── add_reducer.rs │ │ ├── mod.rs │ │ ├── person_table.rs │ │ ├── person_type.rs │ │ └── say_hello_reducer.rs │ ├── basic-ts/ │ │ ├── .template.json │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── main.ts │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── browser-ts/ │ │ ├── .template.json │ │ ├── index.html │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── main.ts │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── bun-ts/ │ │ ├── .template.json │ │ ├── LICENSE │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── main.ts │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── chat-console-cs/ │ │ ├── .template.json │ │ ├── Program.cs │ │ ├── README.md │ │ ├── client.csproj │ │ ├── module_bindings/ │ │ │ ├── Reducers/ │ │ │ │ ├── SendMessage.g.cs │ │ │ │ └── SetName.g.cs │ │ │ ├── SpacetimeDBClient.g.cs │ │ │ ├── Tables/ │ │ │ │ ├── Message.g.cs │ │ │ │ └── User.g.cs │ │ │ └── Types/ │ │ │ ├── Message.g.cs │ │ │ └── User.g.cs │ │ └── spacetimedb/ │ │ ├── Lib.cs │ │ ├── StdbModule.csproj │ │ └── global.json │ ├── chat-console-rs/ │ │ ├── .template.json │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── spacetimedb/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── src/ │ │ ├── main.rs │ │ └── module_bindings/ │ │ ├── message_table.rs │ │ ├── message_type.rs │ │ ├── mod.rs │ │ ├── send_message_reducer.rs │ │ ├── set_name_reducer.rs │ │ ├── user_table.rs │ │ └── user_type.rs │ ├── chat-react-ts/ │ │ ├── .template.json │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── .gitattributes │ │ │ ├── App.css │ │ │ ├── App.integration.test.tsx │ │ │ ├── App.tsx │ │ │ ├── index.css │ │ │ ├── main.tsx │ │ │ ├── module_bindings/ │ │ │ │ ├── index.ts │ │ │ │ ├── message_table.ts │ │ │ │ ├── send_message_reducer.ts │ │ │ │ ├── set_name_reducer.ts │ │ │ │ ├── types/ │ │ │ │ │ ├── procedures.ts │ │ │ │ │ └── reducers.ts │ │ │ │ ├── types.ts │ │ │ │ └── user_table.ts │ │ │ └── setupTests.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ ├── vite.config.ts │ │ └── vitest.config.ts │ ├── deno-ts/ │ │ ├── .template.json │ │ ├── LICENSE │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── src/ │ │ ├── main.ts │ │ └── module_bindings/ │ │ ├── add_reducer.ts │ │ ├── index.ts │ │ ├── person_table.ts │ │ ├── say_hello_reducer.ts │ │ ├── types/ │ │ │ ├── procedures.ts │ │ │ └── reducers.ts │ │ └── types.ts │ ├── keynote-2/ │ │ ├── .dockerignore │ │ ├── .env.example │ │ ├── .gitignore │ │ ├── .prettierrc │ │ ├── DEVELOP.md │ │ ├── Dockerfile.bench │ │ ├── Dockerfile.bun │ │ ├── Dockerfile.rpc │ │ ├── Dockerfile.sqlite-seed │ │ ├── README.md │ │ ├── bun/ │ │ │ └── bun-server.ts │ │ ├── cockroachdb/ │ │ │ ├── docker-compose-crdb-node-1.yml │ │ │ ├── docker-compose-crdb-node-2.yml │ │ │ └── docker-compose-crdb-node-3.yml │ │ ├── convex-app/ │ │ │ ├── .gitignore │ │ │ ├── convex/ │ │ │ │ ├── README.md │ │ │ │ ├── accounts.ts │ │ │ │ ├── balances.ts │ │ │ │ ├── convex.config.ts │ │ │ │ ├── http.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── seed.ts │ │ │ │ ├── transfer.ts │ │ │ │ └── tsconfig.json │ │ │ └── package.json │ │ ├── docker-compose-crdb-loadbalancer.yml │ │ ├── docker-compose-crdb-rpc-server.yml │ │ ├── docker-compose-linux-raid-crdb.yml │ │ ├── docker-compose-linux-raid.yml │ │ ├── docker-compose.yml │ │ ├── module_bindings/ │ │ │ ├── accounts_table.ts │ │ │ ├── index.ts │ │ │ ├── seed_reducer.ts │ │ │ ├── transfer_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ ├── nginx-crdb-local.conf │ │ ├── nginx-crdb.conf │ │ ├── package.json │ │ ├── rust_module/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ ├── spacetimedb/ │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── spacetimedb-rust-client/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── websocket.rs │ │ ├── src/ │ │ │ ├── cli.ts │ │ │ ├── connectors/ │ │ │ │ ├── bun.ts │ │ │ │ ├── convex.ts │ │ │ │ ├── index.ts │ │ │ │ ├── rpc/ │ │ │ │ │ ├── cockroach_rpc.ts │ │ │ │ │ ├── planetscale_pg_rpc.ts │ │ │ │ │ ├── postgres_rpc.ts │ │ │ │ │ ├── rpc_common.ts │ │ │ │ │ ├── sqlite_rpc.ts │ │ │ │ │ └── supabase_rpc.ts │ │ │ │ ├── spacetimedb.ts │ │ │ │ └── sqlite_common.ts │ │ │ ├── core/ │ │ │ │ ├── collision_tracker.ts │ │ │ │ ├── connectors.ts │ │ │ │ ├── runner.ts │ │ │ │ ├── runner_1.ts │ │ │ │ ├── spacetimeMetrics.ts │ │ │ │ ├── types.ts │ │ │ │ └── zipf.ts │ │ │ ├── demo.ts │ │ │ ├── drizzle/ │ │ │ │ └── schema.ts │ │ │ ├── helpers.ts │ │ │ ├── init/ │ │ │ │ ├── init-all.ts │ │ │ │ ├── init_bun.ts │ │ │ │ ├── init_convex.ts │ │ │ │ ├── init_pglike.ts │ │ │ │ ├── init_rpc_servers.ts │ │ │ │ ├── init_spacetime.ts │ │ │ │ ├── init_sqlite.ts │ │ │ │ ├── init_sqlite_seed_in_docker.ts │ │ │ │ ├── init_supabase.ts │ │ │ │ └── utils.ts │ │ │ ├── rpc-servers/ │ │ │ │ ├── cockroach-rpc-server.ts │ │ │ │ ├── postgres-rpc-server.ts │ │ │ │ ├── sqlite-rpc-server.ts │ │ │ │ └── supabase-rpc-server.ts │ │ │ ├── scenario_recipes/ │ │ │ │ ├── reducer_single.ts │ │ │ │ ├── rpc_single_call.ts │ │ │ │ └── sql_single_statement.ts │ │ │ └── tests/ │ │ │ ├── test-1/ │ │ │ │ ├── bun.ts │ │ │ │ ├── cockroach_rpc.ts │ │ │ │ ├── convex.ts │ │ │ │ ├── planetscale_pg_rpc.ts │ │ │ │ ├── postgres_rpc.ts │ │ │ │ ├── spacetimedb.ts │ │ │ │ ├── sqlite_rpc.ts │ │ │ │ └── supabase_rpc.ts │ │ │ └── types.ts │ │ ├── supabase/ │ │ │ ├── .gitignore │ │ │ └── config.toml │ │ ├── tools/ │ │ │ └── benchmarks.html │ │ └── tsconfig.json │ ├── nextjs-ts/ │ │ ├── .template.json │ │ ├── LICENSE │ │ ├── app/ │ │ │ ├── PersonList.tsx │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── providers.tsx │ │ ├── lib/ │ │ │ └── spacetimedb-server.ts │ │ ├── next.config.ts │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── nodejs-ts/ │ │ ├── .template.json │ │ ├── LICENSE │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── main.ts │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── nuxt-ts/ │ │ ├── .gitignore │ │ ├── .template.json │ │ ├── app.vue │ │ ├── components/ │ │ │ └── AppContent.vue │ │ ├── env.d.ts │ │ ├── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ ├── nuxt.config.ts │ │ ├── package.json │ │ ├── server/ │ │ │ └── api/ │ │ │ └── people.get.ts │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── tsconfig.json │ ├── react-ts/ │ │ ├── .template.json │ │ ├── index.html │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── App.tsx │ │ │ ├── main.tsx │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── remix-ts/ │ │ ├── .gitignore │ │ ├── .template.json │ │ ├── LICENSE │ │ ├── app/ │ │ │ ├── lib/ │ │ │ │ └── spacetimedb.server.ts │ │ │ ├── root.tsx │ │ │ └── routes/ │ │ │ └── _index.tsx │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── svelte-ts/ │ │ ├── .template.json │ │ ├── index.html │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── App.svelte │ │ │ ├── Root.svelte │ │ │ ├── main.ts │ │ │ └── module_bindings/ │ │ │ ├── add_reducer.ts │ │ │ ├── index.ts │ │ │ ├── person_table.ts │ │ │ ├── say_hello_reducer.ts │ │ │ ├── types/ │ │ │ │ ├── procedures.ts │ │ │ │ └── reducers.ts │ │ │ └── types.ts │ │ ├── svelte.config.js │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── tanstack-ts/ │ │ ├── .gitignore │ │ ├── .template.json │ │ ├── package.json │ │ ├── spacetimedb/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── src/ │ │ │ ├── lib/ │ │ │ │ └── spacetimedb-server.ts │ │ │ ├── module_bindings/ │ │ │ │ ├── add_reducer.ts │ │ │ │ ├── index.ts │ │ │ │ ├── person_table.ts │ │ │ │ ├── say_hello_reducer.ts │ │ │ │ ├── types/ │ │ │ │ │ ├── procedures.ts │ │ │ │ │ └── reducers.ts │ │ │ │ └── types.ts │ │ │ ├── routeTree.gen.ts │ │ │ ├── router.tsx │ │ │ └── routes/ │ │ │ ├── __root.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── vue-ts/ │ ├── .template.json │ ├── env.d.ts │ ├── index.html │ ├── package.json │ ├── spacetimedb/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── src/ │ │ ├── App.vue │ │ ├── main.ts │ │ └── module_bindings/ │ │ ├── add_reducer.ts │ │ ├── index.ts │ │ ├── person_table.ts │ │ ├── say_hello_reducer.ts │ │ ├── types/ │ │ │ ├── procedures.ts │ │ │ └── reducers.ts │ │ └── types.ts │ ├── tsconfig.json │ └── vite.config.ts ├── tools/ │ ├── check-diff.sh │ ├── ci/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── ci_docs.rs │ │ ├── main.rs │ │ ├── smoketest.rs │ │ └── util.rs │ ├── clippy.sh │ ├── crate-publish-checks.py │ ├── find-publish-list.py │ ├── gen-bindings/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── generate-client-api/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── license-check/ │ │ ├── Cargo.toml │ │ └── main.rs │ ├── llm-oneshot/ │ │ ├── .cursor/ │ │ │ └── rules/ │ │ │ ├── benchmark.mdc │ │ │ ├── deployment.mdc │ │ │ ├── frontend-csharp.mdc │ │ │ ├── frontend-rust.mdc │ │ │ ├── frontend-typescript.mdc │ │ │ ├── grading.mdc │ │ │ ├── patterns-react.mdc │ │ │ ├── patterns-rust.mdc │ │ │ ├── patterns-typescript.mdc │ │ │ ├── postgres.mdc │ │ │ └── spacetimedb-styling.mdc │ │ ├── .gitignore │ │ ├── README.md │ │ ├── apps/ │ │ │ ├── chat-app/ │ │ │ │ ├── prompts/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── base_postgres.md │ │ │ │ │ ├── base_spacetime.md │ │ │ │ │ ├── composed/ │ │ │ │ │ │ ├── 01_basic.md │ │ │ │ │ │ ├── 02_scheduled.md │ │ │ │ │ │ ├── 03_realtime.md │ │ │ │ │ │ ├── 04_reactions.md │ │ │ │ │ │ ├── 05_edit_history.md │ │ │ │ │ │ ├── 06_permissions.md │ │ │ │ │ │ ├── 07_presence.md │ │ │ │ │ │ ├── 08_threading.md │ │ │ │ │ │ ├── 09_private_rooms.md │ │ │ │ │ │ ├── 10_activity.md │ │ │ │ │ │ ├── 11_drafts.md │ │ │ │ │ │ └── 12_full.md │ │ │ │ │ ├── features/ │ │ │ │ │ │ ├── 01_basic.md │ │ │ │ │ │ ├── 02_typing_indicators.md │ │ │ │ │ │ ├── 03_read_receipts.md │ │ │ │ │ │ ├── 04_unread_counts.md │ │ │ │ │ │ ├── 05_scheduled_messages.md │ │ │ │ │ │ ├── 06_ephemeral_messages.md │ │ │ │ │ │ ├── 07_reactions.md │ │ │ │ │ │ ├── 08_edit_history.md │ │ │ │ │ │ ├── 09_realtime_permissions.md │ │ │ │ │ │ ├── 10_rich_presence.md │ │ │ │ │ │ ├── 11_threading.md │ │ │ │ │ │ ├── 12_private_rooms.md │ │ │ │ │ │ ├── 13_activity_indicators.md │ │ │ │ │ │ ├── 14_draft_sync.md │ │ │ │ │ │ └── 15_anonymous_migration.md │ │ │ │ │ ├── grading_checklist.md │ │ │ │ │ ├── grading_rubric.md │ │ │ │ │ ├── language/ │ │ │ │ │ │ ├── csharp-spacetime.md │ │ │ │ │ │ ├── rust-spacetime.md │ │ │ │ │ │ ├── typescript-postgres.md │ │ │ │ │ │ └── typescript-spacetime.md │ │ │ │ │ └── output_instructions.md │ │ │ │ └── typescript/ │ │ │ │ ├── gemini-3-pro/ │ │ │ │ │ ├── postgres/ │ │ │ │ │ │ └── chat-app-20260108-120000/ │ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ │ │ ├── ChatRoom.tsx │ │ │ │ │ │ │ │ │ ├── Layout.tsx │ │ │ │ │ │ │ │ │ ├── Login.tsx │ │ │ │ │ │ │ │ │ ├── MessageInput.tsx │ │ │ │ │ │ │ │ │ ├── MessageItem.tsx │ │ │ │ │ │ │ │ │ └── RoomList.tsx │ │ │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ │ │ ├── socket.ts │ │ │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ │ ├── docker-compose.yml │ │ │ │ │ │ ├── logs.txt │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── check-db.ts │ │ │ │ │ │ │ ├── db/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── spacetime/ │ │ │ │ │ └── chat-app-20260107-120000/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── backend/ │ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── client/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ └── styles.css │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ ├── tsconfig.node.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── gpt-5-2/ │ │ │ │ │ ├── postgres/ │ │ │ │ │ │ └── chat-app-20260108-140800/ │ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ │ ├── api.ts │ │ │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ │ │ ├── socket.ts │ │ │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ │ └── vite-env.d.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ ├── docker-compose.yml │ │ │ │ │ │ ├── drizzle.config.js │ │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ │ ├── env.example │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── auth.ts │ │ │ │ │ │ │ ├── db/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── jobs.ts │ │ │ │ │ │ │ ├── realtime.ts │ │ │ │ │ │ │ └── validate.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── spacetime/ │ │ │ │ │ └── chat-app-20260107-092240/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── backend/ │ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── client/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ └── styles.css │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ ├── tsconfig.node.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── grok-code/ │ │ │ │ │ ├── postgres/ │ │ │ │ │ │ └── chat-app-20260128-112222/ │ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ │ │ ├── Chat.tsx │ │ │ │ │ │ │ │ │ ├── CreateRoomModal.tsx │ │ │ │ │ │ │ │ │ ├── Login.tsx │ │ │ │ │ │ │ │ │ ├── MessageInput.tsx │ │ │ │ │ │ │ │ │ ├── MessageList.tsx │ │ │ │ │ │ │ │ │ ├── OnlineUsers.tsx │ │ │ │ │ │ │ │ │ └── RoomList.tsx │ │ │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ ├── tsconfig.node.json │ │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── db/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── services/ │ │ │ │ │ │ │ │ ├── messageCleanup.ts │ │ │ │ │ │ │ │ └── scheduledMessages.ts │ │ │ │ │ │ │ └── socket/ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── spacetime/ │ │ │ │ │ └── chat-app-20260107-120000/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── backend/ │ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── client/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ ├── ChatArea.tsx │ │ │ │ │ │ │ ├── MessageInput.tsx │ │ │ │ │ │ │ ├── MessageItem.tsx │ │ │ │ │ │ │ ├── Sidebar.tsx │ │ │ │ │ │ │ └── UserSetup.tsx │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ └── main.tsx │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ ├── tsconfig.node.json │ │ │ │ │ └── vite.config.ts │ │ │ │ └── opus-4-5/ │ │ │ │ ├── BENCHMARK_COMPARISON_REPORT.md │ │ │ │ ├── postgres/ │ │ │ │ │ ├── chat-app-20260104-120000/ │ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── nginx.conf │ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ │ ├── api.ts │ │ │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ │ │ ├── socket.ts │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ ├── tsconfig.node.json │ │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ │ ├── docker-compose.yml │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── db/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── push.ts │ │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ ├── chat-app-20260104-160000/ │ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── nginx.conf │ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ │ ├── api.ts │ │ │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ │ ├── docker-compose.yml │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── db.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── chat-app-20260104-180000/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ ├── api.ts │ │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ │ ├── socket.ts │ │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ ├── docker-compose.yml │ │ │ │ │ └── server/ │ │ │ │ │ ├── Dockerfile │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── db.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── schema.ts │ │ │ │ │ └── tsconfig.json │ │ │ │ └── spacetime/ │ │ │ │ ├── chat-app-20260102-162918/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── backend/ │ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── client/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ ├── ChatArea.tsx │ │ │ │ │ │ │ ├── CreateRoomModal.tsx │ │ │ │ │ │ │ ├── EditHistoryModal.tsx │ │ │ │ │ │ │ ├── InvitesPanel.tsx │ │ │ │ │ │ │ ├── MembersPanel.tsx │ │ │ │ │ │ │ ├── MessageInput.tsx │ │ │ │ │ │ │ ├── MessageItem.tsx │ │ │ │ │ │ │ ├── RoomSettingsModal.tsx │ │ │ │ │ │ │ ├── ScheduledMessagesPanel.tsx │ │ │ │ │ │ │ ├── Sidebar.tsx │ │ │ │ │ │ │ ├── StartDmModal.tsx │ │ │ │ │ │ │ ├── StatusDropdown.tsx │ │ │ │ │ │ │ ├── ThreadPanel.tsx │ │ │ │ │ │ │ └── UserSetup.tsx │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ └── styles.css │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ ├── chat-app-20260102-170500/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── backend/ │ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ │ └── main.tsx │ │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ │ └── vite.config.ts │ │ │ │ │ └── docker-compose.yml │ │ │ │ ├── chat-app-20260102-171317/ │ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ │ ├── README.md │ │ │ │ │ ├── backend/ │ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ ├── src/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ └── tsconfig.json │ │ │ │ │ └── client/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ ├── main.tsx │ │ │ │ │ │ └── styles.css │ │ │ │ │ ├── tsconfig.json │ │ │ │ │ └── vite.config.ts │ │ │ │ └── chat-app-20260105-180000/ │ │ │ │ ├── GRADING_RESULTS.md │ │ │ │ ├── README.md │ │ │ │ ├── backend/ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── reducers.ts │ │ │ │ │ │ └── schema.ts │ │ │ │ │ └── tsconfig.json │ │ │ │ └── client/ │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── config.ts │ │ │ │ │ ├── main.tsx │ │ │ │ │ └── styles.css │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ └── paint-app/ │ │ │ ├── prompts/ │ │ │ │ ├── README.md │ │ │ │ ├── base_postgres.md │ │ │ │ ├── base_spacetime.md │ │ │ │ ├── composed/ │ │ │ │ │ ├── 01_basic.md │ │ │ │ │ ├── 02_shapes.md │ │ │ │ │ ├── 03_selection.md │ │ │ │ │ ├── 04_layers.md │ │ │ │ │ ├── 05_presence.md │ │ │ │ │ ├── 06_comments.md │ │ │ │ │ ├── 07_versions.md │ │ │ │ │ ├── 08_permissions.md │ │ │ │ │ ├── 09_follow.md │ │ │ │ │ ├── 10_activity.md │ │ │ │ │ ├── 11_sharing.md │ │ │ │ │ └── 12_full.md │ │ │ │ ├── features/ │ │ │ │ │ ├── 01_basic.md │ │ │ │ │ ├── 02_cursor_indicators.md │ │ │ │ │ ├── 03_shapes.md │ │ │ │ │ ├── 04_selection.md │ │ │ │ │ ├── 05_layers_locking.md │ │ │ │ │ ├── 06_presence.md │ │ │ │ │ ├── 07_comments.md │ │ │ │ │ ├── 08_version_history.md │ │ │ │ │ ├── 09_realtime_permissions.md │ │ │ │ │ ├── 10_follow_mode.md │ │ │ │ │ ├── 11_activity_feed.md │ │ │ │ │ ├── 12_private_canvases.md │ │ │ │ │ ├── 13_canvas_chat.md │ │ │ │ │ ├── 14_auto_cleanup.md │ │ │ │ │ ├── 15_text_sticky.md │ │ │ │ │ └── 16_keyboard_shortcuts.md │ │ │ │ ├── grading_checklist.md │ │ │ │ ├── grading_rubric.md │ │ │ │ ├── language/ │ │ │ │ │ ├── csharp-spacetime.md │ │ │ │ │ ├── rust-spacetime.md │ │ │ │ │ ├── typescript-postgres.md │ │ │ │ │ └── typescript-spacetime.md │ │ │ │ └── output_instructions.md │ │ │ └── typescript/ │ │ │ └── opus-4-5/ │ │ │ └── spacetime/ │ │ │ ├── paint-app-20260109-164112/ │ │ │ │ ├── backend/ │ │ │ │ │ └── spacetimedb/ │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── schema.ts │ │ │ │ │ └── tsconfig.json │ │ │ │ └── client/ │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── config.ts │ │ │ │ │ ├── main.tsx │ │ │ │ │ └── styles.css │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.ts │ │ │ └── paint-app-20260112-154500/ │ │ │ ├── backend/ │ │ │ │ └── spacetimedb/ │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── schema.ts │ │ │ │ └── tsconfig.json │ │ │ └── client/ │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── App.tsx │ │ │ │ ├── config.ts │ │ │ │ ├── main.tsx │ │ │ │ └── styles.css │ │ │ ├── tsconfig.json │ │ │ └── vite.config.ts │ │ ├── package.json │ │ └── scripts/ │ │ └── summarize-grades.ts │ ├── merge-docker-images.sh │ ├── perf.sh │ ├── publish-crates.sh │ ├── replace-spacetimedb/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── lib.rs │ │ └── main.rs │ ├── run-all-tests.sh │ ├── update-test-snapshots.sh │ ├── upgrade-version/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ └── xtask-llm-benchmark/ │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ ├── bench/ │ │ ├── mod.rs │ │ ├── publishers.rs │ │ ├── results_merge.rs │ │ ├── runner.rs │ │ ├── templates.rs │ │ ├── types.rs │ │ └── utils.rs │ ├── benchmarks/ │ │ ├── basics/ │ │ │ ├── t_000_empty_reducers/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_001_basic_tables/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_002_scheduled_table/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_003_struct_in_table/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_004_insert/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_005_update/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_006_delete/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_007_crud/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_008_index_lookup/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_009_init/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ ├── t_010_connect/ │ │ │ │ ├── answers/ │ │ │ │ │ ├── csharp.cs │ │ │ │ │ ├── rust.rs │ │ │ │ │ └── typescript.ts │ │ │ │ ├── spec.rs │ │ │ │ └── tasks/ │ │ │ │ ├── csharp.txt │ │ │ │ ├── rust.txt │ │ │ │ └── typescript.txt │ │ │ └── t_011_helper_function/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ └── schema/ │ │ ├── t_012_spacetime_product_type/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_013_spacetime_sum_type/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_014_elementary_columns/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_015_product_type_columns/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_016_sum_type_columns/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_017_scheduled_columns/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_018_constraints/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_019_many_to_many/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ ├── t_020_ecs/ │ │ │ ├── answers/ │ │ │ │ ├── csharp.cs │ │ │ │ ├── rust.rs │ │ │ │ └── typescript.ts │ │ │ ├── spec.rs │ │ │ └── tasks/ │ │ │ ├── csharp.txt │ │ │ ├── rust.txt │ │ │ └── typescript.txt │ │ └── t_021_multi_column_index/ │ │ ├── answers/ │ │ │ ├── csharp.cs │ │ │ ├── rust.rs │ │ │ └── typescript.ts │ │ ├── spec.rs │ │ └── tasks/ │ │ ├── csharp.txt │ │ ├── rust.txt │ │ └── typescript.txt │ ├── context/ │ │ ├── combine.rs │ │ ├── constants.rs │ │ ├── hashing.rs │ │ ├── mod.rs │ │ └── paths.rs │ ├── eval/ │ │ ├── defaults.rs │ │ ├── lang.rs │ │ ├── mod.rs │ │ ├── scorers.rs │ │ ├── spec.rs │ │ ├── sql_fmt.rs │ │ ├── types.rs │ │ └── utils.rs │ ├── generated/ │ │ ├── mod.rs │ │ └── registry.rs │ ├── lib.rs │ ├── llm/ │ │ ├── clients/ │ │ │ ├── anthropic.rs │ │ │ ├── deepseek.rs │ │ │ ├── google.rs │ │ │ ├── http.rs │ │ │ ├── meta.rs │ │ │ ├── mod.rs │ │ │ ├── openai.rs │ │ │ └── xai.rs │ │ ├── config.rs │ │ ├── mod.rs │ │ ├── model_routes.rs │ │ ├── prompt.rs │ │ ├── provider.rs │ │ ├── segmentation.rs │ │ └── types.rs │ ├── results/ │ │ ├── io.rs │ │ ├── mod.rs │ │ └── schema.rs │ ├── templates/ │ │ ├── csharp/ │ │ │ └── server/ │ │ │ ├── .gitignore │ │ │ ├── Lib.cs │ │ │ └── StdbModule.csproj │ │ ├── rust/ │ │ │ └── server/ │ │ │ ├── .cargo/ │ │ │ │ └── config.toml │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.rs │ │ └── typescript/ │ │ └── server/ │ │ ├── .gitignore │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── tools/ │ └── llm_benchmark_stats_viewer.html └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [build] rustflags = ["--cfg", "tokio_unstable"] [alias] bump-versions = "run -p upgrade-version --" llm = "run --package xtask-llm-benchmark --bin llm_benchmark --" ci = "run -p ci --" smoketest = "ci smoketests --" smoketests = "smoketest" [target.x86_64-pc-windows-msvc] # Use a different linker. Otherwise, the build fails with some obscure linker error that # seems to be a result of us producing a massive PDB file. # I (@bfops) tried a variety of other link options besides switching linkers, but this # seems to be the only thing that worked. linker = "lld-link" # Without this, the linker complains that libc functions are undefined - # it probably signals to rustc and lld-link that libucrt should be included. rustflags = ["-Ctarget-feature=+crt-static"] ================================================ FILE: .dockerignore ================================================ **/target # we do our own version pinning in the Dockerfile rust-toolchain.toml ================================================ FILE: .envrc ================================================ # Directory environment using https://direnv.net/ and https://github.com/nix-community/nix-direnv. use flake ================================================ FILE: .gitattributes ================================================ **/module_bindings/** linguist-generated=true eol=lf /docs/llms/** linguist-generated=true /docs/llms/*-details.json linguist-generated=false ================================================ FILE: .github/CODEOWNERS ================================================ /crates/core/src/db/datastore/traits.rs @cloutiertyler /rust-toolchain.toml @cloutiertyler /.github/CODEOWNERS @cloutiertyler LICENSE @cloutiertyler LICENSE.txt @cloutiertyler /licenses/ @cloutiertyler /crates/client-api-messages/src/websocket.rs @centril @gefjon /crates/cli/src/ @bfops @cloutiertyler @jdetter /tools/ci/ @bfops @cloutiertyler @jdetter /tools/upgrade-version/ @bfops @jdetter /tools/license-check/ @bfops @jdetter /.github/ @bfops @jdetter /crates/sdk/examples/quickstart-chat/ @gefjon /modules/quickstart-chat/ @gefjon ================================================ FILE: .github/Dockerfile ================================================ # Minimal Dockerfile that just wraps pre-built binaries, so we can test the server inside docker FROM rust:1.93.0 RUN mkdir -p /stdb/data COPY ./target/debug/spacetimedb-standalone ./target/debug/spacetimedb-cli /usr/local/bin/ COPY ./crates/standalone/config.toml /stdb/data/config.toml RUN ln -s /usr/local/bin/spacetimedb-cli /usr/local/bin/spacetime ================================================ FILE: .github/GREMLINS.md ================================================ # GREMLINS.md — Who Lives in the Pipes This file documents the automated agents and bots that operate on this repository. ## Clawd 🔧 - **What:** Anti-entropy gremlin / CI watchdog - **GitHub account:** `clockwork-labs-bot` - **Discord channel:** #gremlins (CL - SpacetimeDB) - **Powered by:** [OpenClaw](https://github.com/openclaw/openclaw) + Claude ### What Clawd does - **Monitors CI** — watches for failures, flaky tests, and regressions - **Reviews PRs** — comments on obvious bugs (never approves) - **Surfaces stale PRs** — finds approved PRs that just need a rebase - **Documents testing** — creates and maintains `DEVELOP.md` files explaining CI and test infrastructure - **Alerts the team** — posts findings in #gremlins, pings DevOps when something needs attention ### What Clawd does NOT do - Approve or merge PRs - Take any destructive action (delete branches, close PRs, force push) - Modify production infrastructure ### Contacting Clawd - Mention `@Openclaw` in #gremlins on Discord - Tag `@clockwork-labs-bot` on GitHub PRs/issues --- *To add a new gremlin, document it here.* ================================================ FILE: .github/docker-compose.yml ================================================ services: node: labels: app: spacetimedb build: context: ../ dockerfile: .github/Dockerfile ports: - "3000:3000" # Postgres - "5432:5432" entrypoint: spacetime start --pg-port 5432 privileged: true environment: RUST_BACKTRACE: 1 ================================================ FILE: .github/pull_request_template.md ================================================ # Description of Changes # API and ABI breaking changes # Expected complexity level and risk # Testing - [ ] - [ ] ================================================ FILE: .github/workflows/attach-artifacts.yml ================================================ name: Attach client binaries to release on: workflow_dispatch: inputs: release_tag: description: "Release tag (e.g. v1.9.0)" required: true jobs: upload-assets: runs-on: spacetimedb-new-runner-2 permissions: contents: write # needed to modify releases steps: - name: Checkout uses: actions/checkout@v4 - name: Download artifacts from private base URL env: RELEASE_TAG: ${{ github.event.inputs.release_tag }} BASE_URL: ${{ secrets.ARTIFACT_BASE_URL }} run: | set -euo pipefail FULL_URL="$BASE_URL/$RELEASE_TAG" mkdir -p artifacts cd artifacts download() { local filename="$1" if ! wget -q "${FULL_URL}/${filename}" -O "${filename}"; then echo "Failed to download ${filename}" exit 1 fi } download "spacetime-aarch64-apple-darwin.tar.gz" download "spacetime-aarch64-unknown-linux-gnu.tar.gz" download "spacetime-x86_64-apple-darwin.tar.gz" download "spacetime-x86_64-pc-windows-msvc.zip" download "spacetime-x86_64-unknown-linux-gnu.tar.gz" download "spacetimedb-update-aarch64-apple-darwin" download "spacetimedb-update-aarch64-unknown-linux-gnu" download "spacetimedb-update-x86_64-apple-darwin" download "spacetimedb-update-x86_64-pc-windows-msvc.exe" download "spacetimedb-update-x86_64-unknown-linux-gnu" - name: Upload artifacts to GitHub Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ github.event.inputs.release_tag }} run: | set -euo pipefail cd artifacts gh release upload "$RELEASE_TAG" ./* \ --repo "$GITHUB_REPOSITORY" \ --clobber ================================================ FILE: .github/workflows/benchmarks.yml ================================================ on: push: branches: - master - jgilles/fix-callgrind-again workflow_dispatch: inputs: pr_number: description: 'Pull Request Number' required: false default: '' issue_comment: types: [created] name: Benchmarks env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} jobs: benchmark: name: run criterion benchmarks runs-on: benchmarks-runner # filter for a comment containing 'benchmarks please' if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'benchmarks please')) }} env: PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.issue.number || null }} steps: - name: Clear stdb dir if: always() run: | rm -fr /stdb/* - name: Enable CPU boost run: echo "1" | sudo tee /sys/devices/system/cpu/cpufreq/boost - name: Check membership if: ${{ github.event_name == 'issue_comment' }} env: CONTRIB_ORG: clockworklabs COMMENT_AUTHOR: ${{ github.event.comment.user.login }} ORG_READ_TOKEN: ${{ secrets.ORG_READ_TOKEN }} run: | curl -OL https://github.com/cli/cli/releases/download/v2.37.0/gh_2.37.0_linux_amd64.deb && sudo dpkg -i gh_2.37.0_linux_amd64.deb if [[ $(GH_TOKEN=$ORG_READ_TOKEN gh api --paginate /orgs/{owner}/members --jq 'any(.login == env.COMMENT_AUTHOR)') != true ]]; then gh pr comment $PR_NUMBER -b "Sorry, you don't have permission to run benchmarks." exit 1 fi - name: Post initial comment run: | if [[ $PR_NUMBER ]]; then comment_parent=issues/$PR_NUMBER comment_update=issues/comments else comment_parent=commits/$GITHUB_SHA comment_update=comments fi comment_body="Benchmark in progress..." comment_id=$(gh api "/repos/{owner}/{repo}/$comment_parent/comments" -f body="$comment_body" --jq .id) echo "COMMENT_UPDATE_URL=/repos/{owner}/{repo}/$comment_update/$comment_id" >>$GITHUB_ENV - name: find PR branch if: ${{ env.PR_NUMBER }} run: echo "PR_REF=$(gh pr view $PR_NUMBER --json headRefName --jq .headRefName)" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.PR_REF || github.ref }} # if we're on master we want to know what the sha of HEAD~1 is so # that we can compare results from it to HEAD (in the "Fetch markdown # summary PR" step). otherwise, we can use a fully shallow checkout fetch-depth: ${{ env.PR_NUMBER && 1 || 2 }} - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: profile: minimal components: clippy toolchain: stable target: wasm32-unknown-unknown override: true - name: Install .NET toolchain uses: actions/setup-dotnet@v3 with: global-json-file: global.json env: DOTNET_INSTALL_DIR: ~/.dotnet - name: Build working-directory: crates/bench/ run: | cargo build --release - name: Install latest wasm-opt for module optimisations run: | curl https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz -L | sudo tar xz -C /usr/local --strip-components=1 - name: Disable CPU boost run: echo "0" | sudo tee /sys/devices/system/cpu/cpufreq/boost - name: Branch; run bench run: | if [[ $PR_NUMBER ]]; then BASELINE_NAME=branch RESULTS_NAME=pr-$PR_NUMBER BENCH_FILTER='stdb_raw' echo "Running benchmarks without sqlite" else BASELINE_NAME=master RESULTS_NAME=$GITHUB_SHA BENCH_FILTER='(stdb_raw|sqlite)' echo "Running benchmarks with sqlite" fi pushd crates/bench rm -rf .spacetime cargo bench --bench generic -- --save-baseline "$BASELINE_NAME" "$BENCH_FILTER" # sticker price benchmark cargo bench --bench generic -- --save-baseline "$BASELINE_NAME" 'stdb_module/.*/disk/update_bulk' cargo bench --bench special -- --save-baseline "$BASELINE_NAME" cargo run --bin summarize pack "$BASELINE_NAME" popd mkdir criterion-results [[ ! $PR_NUMBER ]] && cp target/criterion/$BASELINE_NAME.json criterion-results/ cp target/criterion/$BASELINE_NAME.json criterion-results/$RESULTS_NAME.json # this will work for both PR and master - name: Upload criterion results to DO spaces uses: shallwefootball/s3-upload-action@master with: aws_key_id: ${{ secrets.AWS_KEY_ID }} aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} aws_bucket: "spacetimedb-ci-benchmarks" source_dir: criterion-results endpoint: https://nyc3.digitaloceanspaces.com destination_dir: benchmarks - name: Fetch markdown summary PR run: | if [[ $PR_NUMBER ]]; then OLD=master NEW=pr-$PR_NUMBER else OLD=$(git rev-parse HEAD~1) NEW=$GITHUB_SHA fi echo "fetching https://benchmarks.spacetimedb.com/compare/$OLD/$NEW" curl -sS https://benchmarks.spacetimedb.com/compare/$OLD/$NEW > report.md - name: Post comment run: | BODY="
Criterion benchmark results $(cat report.md)
" gh api "$COMMENT_UPDATE_URL" -X PATCH -f body="$BODY" - name: Post failure comment if: ${{ failure() && env.COMMENT_UPDATE_URL }} run: | BODY="Benchmarking failed. Please check [the workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details." gh api "$COMMENT_UPDATE_URL" -X PATCH -f body="$BODY" - name: Clean up if: always() run: | rm -fr /stdb/* callgrind_benchmark: name: run callgrind benchmarks # DON'T run on benchmarks-runner, using docker on a self-hosted runner has # been broken for 4 years: https://github.com/actions/runner/issues/434 . # Fortunately, we can run on standard GitHub Actions infra because we don't care # about other stuff running on the machine! # runs-on: benchmarks-runner runs-on: ubuntu-latest timeout-minutes: 20 # on a successful run, runs in 8 minutes container: image: rust:1.93.0 options: --privileged # filter for a comment containing 'benchmarks please' if: ${{ github.event_name != 'issue_comment' || (github.event.issue.pull_request && contains(github.event.comment.body, 'benchmarks please')) }} env: PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.issue.number || null }} steps: - name: Clear stdb dir if: always() shell: bash run: | rm -fr /stdb/* - name: Install valgrind & iai-callgrind-runner run: | apt-get update apt-get install -y valgrind protobuf-compiler bash sudo curl gh cargo install --git https://github.com/clockworklabs/iai-callgrind.git --branch main iai-callgrind-runner git config --global --add safe.directory /__w/SpacetimeDB/SpacetimeDB # can't do this off self hosted: # - name: Enable CPU boost # shell: bash # run: echo "1" | sudo tee /sys/devices/system/cpu/cpufreq/boost - name: Check membership if: ${{ github.event_name == 'issue_comment' }} env: CONTRIB_ORG: clockworklabs COMMENT_AUTHOR: ${{ github.event.comment.user.login }} ORG_READ_TOKEN: ${{ secrets.ORG_READ_TOKEN }} shell: bash run: | curl -OL https://github.com/cli/cli/releases/download/v2.37.0/gh_2.37.0_linux_amd64.deb && sudo dpkg -i gh_2.37.0_linux_amd64.deb if [[ $(GH_TOKEN=$ORG_READ_TOKEN gh api --paginate /orgs/{owner}/members --jq 'any(.login == env.COMMENT_AUTHOR)') != true ]]; then gh pr comment $PR_NUMBER -b "Sorry, you don't have permission to run benchmarks." exit 1 fi - name: find PR branch if: ${{ env.PR_NUMBER }} shell: bash run: echo "PR_REF=$(gh pr view $PR_NUMBER --json headRefName --jq .headRefName)" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v3 with: ref: ${{ env.PR_REF || github.ref }} # if we're on master we want to know what the sha of HEAD~1 is so # that we can compare results from it to HEAD (in the "Fetch markdown # summary PR" step). otherwise, we can use a fully shallow checkout fetch-depth: ${{ env.PR_NUMBER && 1 || 2 }} - name: Unbork GitHub Actions state shell: bash run: | echo "Letting anybody touch our git repo, in order to avoid breaking other jobs" echo "This is necessary because we are running as root inside a docker image" chmod -R a+rw . - name: Post initial comment shell: bash run: | set -exo pipefail if [[ $PR_NUMBER ]]; then comment_parent=issues/$PR_NUMBER comment_update=issues/comments else comment_parent=commits/$GITHUB_SHA comment_update=comments fi comment_body="Callgrind benchmark in progress..." comment_id=$(gh api "/repos/{owner}/{repo}/$comment_parent/comments" -f body="$comment_body" --jq .id) echo "COMMENT_UPDATE_URL=/repos/{owner}/{repo}/$comment_update/$comment_id" >>$GITHUB_ENV - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal components: clippy toolchain: stable target: wasm32-unknown-unknown override: true - name: Build working-directory: crates/bench/ shell: bash run: | cargo build --release - name: Install latest wasm-opt for module optimisations shell: bash run: | curl https://github.com/WebAssembly/binaryen/releases/download/version_116/binaryen-version_116-x86_64-linux.tar.gz -L | sudo tar xz -C /usr/local --strip-components=1 # leave CPU boost on, doesn't affect callgrind! - name: Branch; run bench shell: bash run: | if [[ $PR_NUMBER ]]; then BASELINE_NAME=branch RESULTS_NAME=pr-$PR_NUMBER BENCH_FILTER='(special|stdb_module|stdb_raw)' echo "Running branch callgrind benchmarks" else BASELINE_NAME=master RESULTS_NAME=$GITHUB_SHA BENCH_FILTER='.*' echo "Running master callgrind benchmarks" fi pushd crates/bench rm -rf .spacetime cargo bench --bench callgrind -- --save-summary pretty-json cargo run --bin summarize pack-callgrind "$BASELINE_NAME" popd mkdir callgrind-results [[ ! $PR_NUMBER ]] && cp target/iai/$BASELINE_NAME.json callgrind-results/ cp target/iai/$BASELINE_NAME.json callgrind-results/$RESULTS_NAME.json # this will work for both PR and master - name: Upload callgrind results to DO spaces uses: shallwefootball/s3-upload-action@master with: aws_key_id: ${{ secrets.AWS_KEY_ID }} aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} aws_bucket: "spacetimedb-ci-benchmarks" source_dir: callgrind-results endpoint: https://nyc3.digitaloceanspaces.com destination_dir: callgrind-benchmarks - name: Fetch markdown summary PR shell: bash run: | if [[ $PR_NUMBER ]]; then OLD=master NEW=pr-$PR_NUMBER else OLD=$(git rev-parse HEAD~1) NEW=$GITHUB_SHA fi echo "fetching https://benchmarks.spacetimedb.com/compare_callgrind/$OLD/$NEW" curl -sS https://benchmarks.spacetimedb.com/compare_callgrind/$OLD/$NEW > report.md - name: Post comment shell: bash run: | BODY="
Callgrind benchmark results $(cat report.md)
" gh api "$COMMENT_UPDATE_URL" -X PATCH -f body="$BODY" - name: Post failure comment if: ${{ failure() && env.COMMENT_UPDATE_URL }} shell: bash run: | BODY="Benchmarking failed. Please check [the workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details." gh api "$COMMENT_UPDATE_URL" -X PATCH -f body="$BODY" - name: Clean up if: always() shell: bash run: | rm -fr /stdb/* echo "Letting anybody touch our git repo, in order to avoid breaking other jobs" echo "This is necessary because we are running as root inside a docker image" chmod -R a+rw . ================================================ FILE: .github/workflows/check-merge-labels.yml ================================================ name: Check merge labels on: pull_request: types: [opened, reopened, synchronize, labeled, unlabeled] merge_group: permissions: read-all jobs: label_checks: name: Check merge labels runs-on: ubuntu-latest steps: - if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'do not merge') run: | echo "This is labeled \"Do not merge\"." exit 1 # If we're in a merge queue, the PR has already passed checks these checks before being added to the queue. - if: github.event_name == 'merge_group' run: echo "Merge group run; skipping merge-label checks." ================================================ FILE: .github/workflows/check-pr-base.yml ================================================ name: Git tree checks on: pull_request: types: [opened, edited] merge_group: permissions: read-all jobs: check_base_ref: name: Based on `master` runs-on: ubuntu-latest steps: - id: not_based_on_master if: | github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'master' run: | echo "This PR is not based on master. Please wait until the base PR merges." exit 1 ================================================ FILE: .github/workflows/ci.yml ================================================ on: pull_request: push: branches: - master merge_group: workflow_dispatch: inputs: pr_number: description: "Pull Request Number" required: false default: "" name: CI concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.inputs.pr_number || format('sha-{0}', github.sha) }} cancel-in-progress: true jobs: smoketests: needs: [lints] name: Smoketests (${{ matrix.name }}) strategy: matrix: include: - name: Linux runner: spacetimedb-new-runner-2 - name: Windows runner: windows-latest runs-on: ${{ matrix.runner }} timeout-minutes: 120 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target SPACETIMEDB_CPP_DIR: ${{ github.workspace }}/crates/bindings-cpp steps: - name: Find Git ref env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | PR_NUMBER="${{ github.event.inputs.pr_number || null }}" if test -n "${PR_NUMBER}"; then GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" else GIT_REF="${{ github.ref }}" fi echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb cache-on-failure: false cache-all-crates: true cache-workspace-crates: true prefix-key: v1 - uses: actions/setup-dotnet@v4 with: global-json-file: global.json # nodejs and pnpm are required for the typescript quickstart smoketest - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 18 - uses: pnpm/action-setup@v4 with: run_install: true # Install emscripten for C++ module compilation tests. - name: Install emscripten (Linux) if: runner.os == 'Linux' shell: bash run: | git clone https://github.com/emscripten-core/emsdk.git ~/emsdk cd ~/emsdk ./emsdk install 4.0.21 ./emsdk activate 4.0.21 - name: Install emscripten (Windows) if: runner.os == 'Windows' shell: pwsh run: | git clone https://github.com/emscripten-core/emsdk.git $env:USERPROFILE\emsdk cd $env:USERPROFILE\emsdk .\emsdk install 4.0.21 .\emsdk activate 4.0.21 - name: Install psql (Windows) if: runner.os == 'Windows' shell: pwsh run: | # Fail properly if any individual command fails $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true choco install psql -y --no-progress # Check for existence, since `choco` doesn't seem to fail the step if it fails to install.. # See https://github.com/clockworklabs/SpacetimeDB/pull/4399 for more background. Get-Command psql - name: Update dotnet workloads if: runner.os == 'Windows' run: | # Fail properly if any individual command fails $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true cd modules # the sdk-manifests on windows-latest are messed up, so we need to update them dotnet workload config --update-mode manifests dotnet workload update - name: Override NuGet packages shell: bash run: | dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime dotnet pack -c Release crates/bindings-csharp/Runtime cd sdks/csharp ./tools~/write-nuget-config.sh ../.. # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with # `cargo build --manifest-path` (which apparently build different dependency trees). # However, we've been unable to fix it so... /shrug - name: Check v8 outputs shell: bash run: | find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean -p v8 || true cargo build -p v8 fi - name: Install cargo-nextest uses: taiki-e/install-action@nextest # --test-threads=1 eliminates contention in the C# tests where they fight over bindings # build artifacts. # It also seemed to improve performance a fair amount (11m -> 6m) - name: Run smoketests (Linux) if: runner.os == 'Linux' shell: bash run: | if [ -f ~/emsdk/emsdk_env.sh ]; then source ~/emsdk/emsdk_env.sh fi cargo ci smoketests -- --test-threads=1 # Due to Emscripten PATH issues this was separated to make sure OpenSSL still builds correctly - name: Run smoketests (Windows) if: runner.os == 'Windows' shell: pwsh run: | if (Test-Path "$env:USERPROFILE\emsdk\emsdk_env.ps1") { & "$env:USERPROFILE\emsdk\emsdk_env.ps1" | Out-Null } cargo ci smoketests -- --test-threads=1 - name: Check for changes run: | tools/check-diff.sh crates/smoketests || { echo 'Error: There is a diff in the smoketests directory.' exit 1 } test: needs: [lints] name: Test Suite runs-on: spacetimedb-new-runner-2 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - name: Find Git ref env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR_NUMBER="${{ github.event.inputs.pr_number || null }}" if test -n "${PR_NUMBER}"; then GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" else GIT_REF="${{ github.ref }}" fi echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the smoketests job save the cache since it builds the most things save-if: false prefix-key: v1 - uses: actions/setup-dotnet@v3 with: global-json-file: global.json - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 - uses: pnpm/action-setup@v4 with: run_install: true # Install cmake and emscripten for C++ module compilation tests. - name: Install cmake and emscripten run: | sudo apt-get update sudo apt-get install -y cmake git clone https://github.com/emscripten-core/emsdk.git ~/emsdk cd ~/emsdk ./emsdk install 4.0.21 ./emsdk activate 4.0.21 - name: Build typescript module sdk working-directory: crates/bindings-typescript run: pnpm build # Source emsdk environment to make emcc (Emscripten compiler) available in PATH. - name: Run tests run: | source ~/emsdk/emsdk_env.sh cargo ci test lints: name: Lints runs-on: spacetimedb-new-runner-2 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - name: Checkout sources uses: actions/checkout@v3 - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - run: echo ::add-matcher::.github/workflows/rust_matcher.json - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the smoketests job save the cache since it builds the most things save-if: false prefix-key: v1 - uses: actions/setup-dotnet@v3 with: global-json-file: global.json - name: Run ci lint run: cargo ci lint wasm_bindings: name: Build and test wasm bindings runs-on: spacetimedb-new-runner-2 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - uses: actions/checkout@v3 - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - run: echo ::add-matcher::.github/workflows/rust_matcher.json - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the smoketests job save the cache since it builds the most things save-if: false prefix-key: v1 - name: Run bindgen tests run: cargo ci wasm-bindings publish_checks: name: Check that packages are publishable runs-on: spacetimedb-new-runner-2 permissions: read-all steps: - uses: actions/checkout@v3 - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Set up Python env run: | test -d venv || python3 -m venv venv venv/bin/pip3 install argparse toml - name: Run checks run: | set -ueo pipefail FAILED=0 ROOTS=(spacetimedb spacetimedb-sdk) CRATES=$(venv/bin/python3 tools/find-publish-list.py --recursive --directories --quiet "${ROOTS[@]}") for crate_dir in $CRATES; do if ! venv/bin/python3 tools/crate-publish-checks.py "${crate_dir}"; then FAILED=$(( $FAILED + 1 )) fi done if [ $FAILED -gt 0 ]; then exit 1 fi update: name: Test spacetimedb-update flow (${{ matrix.target }}) permissions: read-all strategy: matrix: include: - { target: x86_64-unknown-linux-gnu, runner: spacetimedb-new-runner-2 } - { target: aarch64-unknown-linux-gnu, runner: arm-runner } - { target: aarch64-apple-darwin, runner: macos-latest } - { target: x86_64-pc-windows-msvc, runner: windows-latest } runs-on: ${{ matrix.runner }} steps: - name: Checkout uses: actions/checkout@v3 - name: Install Rust uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Install rust target run: rustup target add ${{ matrix.target }} - name: Install packages if: ${{ matrix.runner == 'arm-runner' }} shell: bash run: sudo apt install -y libssl-dev - name: Build spacetimedb-update run: cargo build --features github-token-auth --target ${{ matrix.target }} -p spacetimedb-update if: runner.os == 'Windows' - name: Run self-install env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | ROOT_DIR="$(mktemp -d)" # NOTE(bfops): We need the `github-token-auth` feature because we otherwise tend to get ratelimited when we try to fetch `/releases/latest`. # My best guess is that, on the GitHub runners, the "anonymous" ratelimit is shared by *all* users of that runner (I think this because it # happens very frequently on the `macos-runner`, but we haven't seen it on any others). cargo run --features github-token-auth --target ${{ matrix.target }} -p spacetimedb-update -- self-install --root-dir="${ROOT_DIR}" --yes "${ROOT_DIR}"/spacetime --root-dir="${ROOT_DIR}" help if: runner.os == 'Windows' - name: Test spacetimedb-update env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: cargo ci update-flow --target=${{ matrix.target }} --github-token-auth if: runner.os != 'Windows' unreal_engine_tests: name: Unreal Engine Tests # This can't go on e.g. ubuntu-latest because that runner runs out of disk space. ChatGPT suggested that the general solution tends to be to use # a custom runner. runs-on: spacetimedb-new-runner-2 # Disable the tests because they are very flaky at the moment. # TODO: Remove this line and re-enable the `if` line just below here. if: false # Skip if this is an external contribution. GitHub secrets will be empty, so the step would fail anyway. # if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} container: image: ghcr.io/epicgames/unreal-engine:dev-5.6 credentials: # Note(bfops): I don't think that `github.actor` needs to match the user that the token is for, because I'm using a token for my account and # it seems to be totally happy. # However, the token needs to be for a user that has access to the EpicGames org (see # https://dev.epicgames.com/documentation/en-us/unreal-engine/downloading-source-code-in-unreal-engine?application_version=5.6) username: ${{ github.actor }} password: ${{ secrets.GHCR_TOKEN }} # Run as root because otherwise we get permission denied for various directories inside the container. I tried doing dances to allow it to run # without this (reassigning env vars and stuff), but was unable to get it to work and it felt like an uphill battle. options: --user 0:0 steps: # Uncomment this before merging so that it will run properly if run manually through the GH actions flow. It was playing weird with rolled back # commits though. # - name: Find Git ref # env: # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # shell: bash # run: | # PR_NUMBER="${{ github.event.inputs.pr_number || null }}" # if test -n "${PR_NUMBER}"; then # GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" # else # GIT_REF="${{ github.ref }}" # fi # echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Run Unreal Engine tests working-directory: sdks/unreal env: UE_ROOT_PATH: /home/ue4/UnrealEngine run: | apt-get update apt-get install -y acl curl ca-certificates REPO="$GITHUB_WORKSPACE" # Let ue4 read/write the workspace & tool caches without changing ownership for p in "$REPO" "${RUNNER_TEMP:-/__t}" "${RUNNER_TOOL_CACHE:-/__t}"; do [ -d "$p" ] && setfacl -R -m u:ue4:rwX -m d:u:ue4:rwX "$p" || true done # Rust tool caches live under the runner tool cache so they persist export CARGO_HOME="${RUNNER_TOOL_CACHE:-/__t}/cargo" export RUSTUP_HOME="${RUNNER_TOOL_CACHE:-/__t}/rustup" mkdir -p "$CARGO_HOME" "$RUSTUP_HOME" chown -R ue4:ue4 "$CARGO_HOME" "$RUSTUP_HOME" # Make sure the UE build script is executable (and parents traversable) UE_DIR="${UE_ROOT_PATH:-/home/ue4/UnrealEngine}" chmod a+rx "$UE_DIR" "$UE_DIR/Engine" "$UE_DIR/Engine/Build" "$UE_DIR/Engine/Build/BatchFiles/Linux" || true chmod a+rx "$UE_DIR/Engine/Build/BatchFiles/Linux/Build.sh" || true # Run the build & tests as ue4 (who owns the UE tree) sudo -E -H -u ue4 env \ HOME=/home/ue4 \ XDG_CONFIG_HOME=/home/ue4/.config \ CARGO_HOME="$CARGO_HOME" \ RUSTUP_HOME="$RUSTUP_HOME" \ PATH="$CARGO_HOME/bin:$PATH" \ bash -lc ' set -euxo pipefail # Install rustup for ue4 if needed (uses the shared caches) if ! command -v cargo >/dev/null 2>&1; then curl -sSf https://sh.rustup.rs | sh -s -- -y fi rustup show >/dev/null git config --global --add safe.directory "$GITHUB_WORKSPACE" || true cd "$GITHUB_WORKSPACE/sdks/unreal" cargo --version cargo test -- --test-threads=1 ' ci_command_docs: name: Check CI command docs runs-on: spacetimedb-new-runner-2 steps: - name: Find Git ref env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | PR_NUMBER="${{ github.event.inputs.pr_number || null }}" if test -n "${PR_NUMBER}"; then GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" else GIT_REF="${{ github.ref }}" fi echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Check for docs change run: cargo ci self-docs --check cli_docs: name: Check CLI docs permissions: read-all runs-on: spacetimedb-new-runner-2 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - name: Find Git ref env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | PR_NUMBER="${{ github.event.inputs.pr_number || null }}" if test -n "${PR_NUMBER}"; then GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" else GIT_REF="${{ github.ref }}" fi echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 - uses: pnpm/action-setup@v4 with: run_install: true - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the smoketests job save the cache since it builds the most things save-if: false prefix-key: v1 - name: Check for docs change run: | cargo ci cli-docs llm_ci_check: name: Verify LLM benchmark is up to date permissions: contents: read runs-on: ubuntu-latest # Disable the tests because they are causing us headaches with merge conflicts and re-runs etc. if: false steps: # Build the tool from master to ensure consistent hash computation # with the llm-benchmark-update workflow (which also uses master's tool). - name: Checkout master (build tool from trusted code) uses: actions/checkout@v4 with: ref: master fetch-depth: 1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Install llm-benchmark tool from master run: | cargo install --path tools/xtask-llm-benchmark --locked command -v llm_benchmark # Now checkout the PR branch to verify its benchmark files - name: Checkout PR branch uses: actions/checkout@v4 with: clean: false - name: Run hash check (both langs) run: llm_benchmark ci-check unity-testsuite: needs: [lints] # Skip if this is an external contribution. # The license secrets will be empty, so the step would fail anyway. if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} permissions: contents: read checks: write runs-on: spacetimedb-unity-runner timeout-minutes: 30 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - name: Checkout repository id: checkout-stdb uses: actions/checkout@v4 # Run cheap .NET tests first. If those fail, no need to run expensive Unity tests. - name: Setup dotnet uses: actions/setup-dotnet@v3 with: global-json-file: global.json - name: Override NuGet packages run: | dotnet pack crates/bindings-csharp/BSATN.Runtime dotnet pack crates/bindings-csharp/Runtime # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. # This means that (if version numbers match) we will test the local versions of the C# packages, even # if they're not pushed to NuGet. # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. cd sdks/csharp ./tools~/write-nuget-config.sh ../.. - name: Restore .NET solution working-directory: sdks/csharp run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln # Now, setup the Unity tests. - name: Patch spacetimedb dependency in Cargo.toml working-directory: demo/Blackholio/server-rust run: | sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml cat Cargo.toml - name: Install Rust toolchain uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the main CI job save the cache since it builds the most things save-if: false prefix-key: v1 # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with # `cargo build --manifest-path` (which apparently build different dependency trees). # However, we've been unable to fix it so... /shrug - name: Check v8 outputs run: | find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean --release -p v8 || true cargo build --release -p v8 fi - name: Install SpacetimeDB CLI from the local checkout run: | export CARGO_HOME="$HOME/.cargo" echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" cargo install --force --path crates/cli --locked --message-format=short cargo install --force --path crates/standalone --locked --message-format=short # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - name: Generate client bindings working-directory: demo/Blackholio/server-rust run: bash ./generate.sh -y - name: Check for changes run: | tools/check-diff.sh demo/Blackholio/client-unity/Assets/Scripts/autogen || { echo 'Error: Bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.' exit 1 } - name: Hydrate Unity SDK DLLs run: cargo ci dlls - name: Check Unity meta files uses: DeNA/unity-meta-check@v3 with: enable_pr_comment: ${{ github.event_name == 'pull_request' }} target_path: sdks/csharp env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Start SpacetimeDB run: | spacetime start & disown - name: Publish unity-tests module to SpacetimeDB working-directory: demo/Blackholio/server-rust run: | spacetime logout && spacetime login --server-issued-login local bash ./publish.sh - name: Patch com.clockworklabs.spacetimedbsdk dependency in manifest.json working-directory: demo/Blackholio/client-unity/Packages run: | yq e -i '.dependencies["com.clockworklabs.spacetimedbsdk"] = "file:../../../../sdks/csharp"' manifest.json cat manifest.json - uses: actions/cache@v3 with: path: demo/Blackholio/client-unity/Library key: Unity-${{ github.head_ref }} restore-keys: Unity- - name: Login to DockerHub uses: docker/login-action@v2 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Run Unity tests uses: game-ci/unity-test-runner@v4 with: unityVersion: 2022.3.32f1 # Adjust Unity version to a valid tag projectPath: demo/Blackholio/client-unity # Path to the Unity project subdirectory githubToken: ${{ secrets.GITHUB_TOKEN }} testMode: playmode useHostNetwork: true artifactsPath: "" env: UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} csharp-testsuite: needs: [lints] runs-on: spacetimedb-new-runner-2 timeout-minutes: 30 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - name: Checkout repository id: checkout-stdb uses: actions/checkout@v4 # Run cheap .NET tests first. If those fail, no need to run expensive Unity tests. - name: Setup dotnet uses: actions/setup-dotnet@v3 with: global-json-file: global.json - name: Override NuGet packages run: | dotnet pack crates/bindings-csharp/BSATN.Runtime dotnet pack crates/bindings-csharp/Runtime # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. # This means that (if version numbers match) we will test the local versions of the C# packages, even # if they're not pushed to NuGet. # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. cd sdks/csharp ./tools~/write-nuget-config.sh ../.. - name: Restore .NET solution working-directory: sdks/csharp run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln - name: Run .NET tests working-directory: sdks/csharp run: dotnet test -warnaserror --no-restore - name: Verify C# formatting working-directory: sdks/csharp run: dotnet format --no-restore --verify-no-changes SpacetimeDB.ClientSDK.sln - name: Install Rust toolchain uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the main CI job save the cache since it builds the most things save-if: false prefix-key: v1 # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with # `cargo build --manifest-path` (which apparently build different dependency trees). # However, we've been unable to fix it so... /shrug - name: Check v8 outputs run: | find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean -p v8 || true cargo build -p v8 fi find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean --release -p v8 || true cargo build --release -p v8 fi - name: Install SpacetimeDB CLI from the local checkout run: | export CARGO_HOME="$HOME/.cargo" echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" cargo install --force --path crates/cli --locked --message-format=short cargo install --force --path crates/standalone --features allow_loopback_http_for_tests --locked --message-format=short # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - name: Check quickstart-chat bindings are up to date working-directory: sdks/csharp run: | bash tools~/gen-quickstart.sh "${GITHUB_WORKSPACE}"/tools/check-diff.sh examples~/quickstart-chat || { echo 'Error: quickstart-chat bindings have changed. Please run `sdks/csharp/tools~/gen-quickstart.sh`.' exit 1 } # TODO: Re-enable this once csharp is using the v2 ws api. # - name: Check client-api bindings are up to date # working-directory: sdks/csharp # run: | # bash tools~/gen-client-api.sh # "${GITHUB_WORKSPACE}"/tools/check-diff.sh src/SpacetimeDB/ClientApi || { # echo 'Error: Client API bindings are dirty. Please run `sdks/csharp/tools~/gen-client-api.sh`.' # exit 1 # } - name: Start SpacetimeDB run: | spacetime start & disown - name: Run regression tests run: | bash sdks/csharp/tools~/run-regression-tests.sh tools/check-diff.sh sdks/csharp/examples~/regression-tests || { echo 'Error: Bindings are dirty. Please run `sdks/csharp/tools~/gen-regression-tests.sh`.' exit 1 } internal-tests: name: Internal Tests needs: [lints] # Skip if not a PR or a push to master # Skip if this is an external contribution. GitHub secrets will be empty, so the step would fail anyway. if: ${{ (github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/master')) && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} permissions: contents: read runs-on: ubuntu-latest env: TARGET_OWNER: clockworklabs TARGET_REPO: SpacetimeDBPrivate steps: - id: dispatch name: Trigger tests uses: actions/github-script@v7 with: github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }} script: | const workflowId = 'ci.yml'; const targetRef = 'master'; const targetOwner = process.env.TARGET_OWNER; const targetRepo = process.env.TARGET_REPO; // Use the ref for pull requests because the head sha is brittle (github does some extra dance where it merges in master). const publicRef = (context.eventName === 'pull_request') ? context.payload.pull_request.head.ref : context.sha; const preDispatch = new Date().toISOString(); // Dispatch the workflow in the target repository await github.rest.actions.createWorkflowDispatch({ owner: targetOwner, repo: targetRepo, workflow_id: workflowId, ref: targetRef, inputs: { public_ref: publicRef } }); const sleep = (ms) => new Promise(r => setTimeout(r, ms)); // Find the dispatched run by name let runId = null; for (let attempt = 0; attempt < 20 && !runId; attempt++) { // up to ~10 minutes to locate the run await sleep(5000); const runsResp = await github.rest.actions.listWorkflowRuns({ owner: targetOwner, repo: targetRepo, workflow_id: workflowId, event: 'workflow_dispatch', branch: targetRef, per_page: 50, }); const expectedName = `CI [public_ref=${publicRef}]`; const candidates = runsResp.data.workflow_runs .filter(r => r.name === expectedName && new Date(r.created_at) >= new Date(preDispatch)) .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); if (candidates.length > 0) { runId = candidates[0].id; break; } } if (!runId) { core.setFailed('Failed to locate dispatched run in the private repository.'); return; } const runUrl = `https://github.com/${targetOwner}/${targetRepo}/actions/runs/${runId}`; core.info(`View run: ${runUrl}`); core.setOutput('run_id', String(runId)); core.setOutput('run_url', runUrl); - name: Wait for Internal Tests to complete uses: actions/github-script@v7 with: github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }} script: | const targetOwner = process.env.TARGET_OWNER; const targetRepo = process.env.TARGET_REPO; const runId = Number(`${{ steps.dispatch.outputs.run_id }}`); const runUrl = `${{ steps.dispatch.outputs.run_url }}`; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); core.info(`Waiting for workflow result... ${runUrl}`); let conclusion = null; for (let attempt = 0; attempt < 240; attempt++) { // up to ~2 hours const runResp = await github.rest.actions.getWorkflowRun({ owner: targetOwner, repo: targetRepo, run_id: runId }); const { status, conclusion: c } = runResp.data; if (status === 'completed') { conclusion = c || 'success'; break; } await sleep(30000); } if (!conclusion) { core.setFailed('Timed out waiting for private workflow to complete.'); return; } if (conclusion !== 'success') { core.setFailed(`Private workflow failed with conclusion: ${conclusion}`); } - name: Cancel invoked run if workflow cancelled if: ${{ cancelled() }} uses: actions/github-script@v7 with: github-token: ${{ secrets.SPACETIMEDB_PRIVATE_TOKEN }} script: | const targetOwner = process.env.TARGET_OWNER; const targetRepo = process.env.TARGET_REPO; const runId = Number(`${{ steps.dispatch.outputs.run_id }}`); if (!runId) return; await github.rest.actions.cancelWorkflowRun({ owner: targetOwner, repo: targetRepo, run_id: runId, }); global_json_policy: name: Verify global.json files are symlinks runs-on: ubuntu-latest permissions: contents: read steps: - name: Find Git ref env: PR_NUMBER: ${{ github.event.inputs.pr_number }} run: | if [ -n "$PR_NUMBER" ]; then GIT_REF="refs/pull/$PR_NUMBER/merge" else GIT_REF="${{ github.ref }}" fi echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb save-if: false prefix-key: v1 - name: Check global.json policy run: cargo ci global-json-policy warn-python-smoketests: name: Check for Python smoketest edits runs-on: ubuntu-latest if: github.event_name == 'pull_request' permissions: contents: read steps: - name: Checkout sources uses: actions/checkout@v4 with: fetch-depth: 0 - name: Fail if Python smoketests were modified run: | MERGE_BASE="$(git merge-base origin/${{ github.base_ref }} HEAD)" PYTHON_SMOKETEST_CHANGES="$(git diff --name-only "$MERGE_BASE" HEAD -- 'smoketests/**.py')" if [ -n "$PYTHON_SMOKETEST_CHANGES" ]; then echo "::error::This PR modifies legacy Python smoketests. Please add new tests to the Rust smoketests in crates/smoketests/ instead." echo "" echo "Changed files:" echo "$PYTHON_SMOKETEST_CHANGES" echo "" echo "The Python smoketests are being replaced by Rust smoketests." echo "See crates/smoketests/DEVELOP.md for instructions on adding Rust smoketests." exit 1 fi echo "No Python smoketest changes detected." smoketests_mod_rs_complete: name: Check smoketests/mod.rs is complete runs-on: ubuntu-latest permissions: contents: read env: CARGO_TARGET_DIR: ${{ github.workspace }}/target steps: - name: Find Git ref env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | PR_NUMBER="${{ github.event.inputs.pr_number || null }}" if test -n "${PR_NUMBER}"; then GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" else GIT_REF="${{ github.ref }}" fi echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - name: Checkout sources uses: actions/checkout@v4 with: ref: ${{ env.GIT_REF }} - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb cache-on-failure: false cache-all-crates: true cache-workspace-crates: true prefix-key: v1 # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with # `cargo build --manifest-path` (which apparently build different dependency trees). # However, we've been unable to fix it so... /shrug - name: Check v8 outputs shell: bash run: | find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean -p v8 || true cargo build -p v8 fi - name: Verify crates/smoketests/tests/smoketests/mod.rs lists all entries run: | cargo ci smoketests check-mod-list ================================================ FILE: .github/workflows/discord-posts.yml ================================================ name: Discord notifications on: pull_request: types: [closed] jobs: discordNotification: runs-on: ubuntu-latest if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Set up GitHub CLI run: | curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /usr/share/keyrings/githubcli-archive-keyring.gpg > /dev/null sudo apt-get install -y apt-transport-https echo "deb [arch=amd64 signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list sudo apt-get update sudo apt-get install -y gh - name: Send Discord notification env: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} PR_TITLE: ${{ github.event.pull_request.title }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_URL: ${{ github.event.pull_request.html_url }} MENTION_ON_FAILURE: ${{ secrets.DEV_OPS_ROLE_ID }} DISCORD_USER_MAP: ${{ secrets.DISCORD_USER_MAP }} run: | message="PR merged: [(#${PR_NUMBER}) ${PR_TITLE}](<${PR_URL}>)" # Note that anything besides success is treated as a failure (e.g. if the check did not run at all, or if it is still pending). FAILED_CHECKS="$( gh pr checks "${{github.event.pull_request.html_url}}" \ --json 'workflow,state,name' | jq '.[] | select(.workflow != "Discord notifications") | select(.state != "SUCCESS" and .state != "NEUTRAL" and .state != "SKIPPED") ' | jq -r '"\(.workflow) / \(.name): \(.state)"' )" # Lookup PR author's Discord ID from the provided JSON map (if any) author_discord_id="$( jq -r \ --arg u "${{ github.event.pull_request.user.login }}" \ '.[$u] // empty' \ <<<"${DISCORD_USER_MAP}" )" if [ -z "${author_discord_id}" ]; then echo "Warning: PR author not found not found in USER_LOOKUP_JSON" fi message+=$'\n' if [[ -z "${FAILED_CHECKS}" ]]; then message+='All checks passed.' else message+="${FAILED_CHECKS}" message+=$'\n' # This uses special Discord syntax for pinging a particular role. # Note the '&' - this is the difference between pinging a *role* and pinging a *person*. if [[ -n "${author_discord_id}" ]]; then message+="<@${author_discord_id}> please investigate these failures." fi message+=$'\n' message+="(cc <@&${MENTION_ON_FAILURE}> - Releases may be affected)" fi # Use `jq` to construct the json data blob in the format required by the webhook. data="$(jq --null-input --arg msg "$message" '.content=$msg')" curl -X POST -H 'Content-Type: application/json' -d "$data" "${DISCORD_WEBHOOK_URL}" ================================================ FILE: .github/workflows/docker.yml ================================================ name: Docker Image on: push: branches: - master - staging - dev tags: - 'v*' jobs: docker-amd64: runs-on: ubuntu-latest name: Build DockerHub AMD64 Container steps: - name: Checkout uses: actions/checkout@v3 - name: Docker meta id: meta uses: docker/metadata-action@v4 with: images: | clockworklabs/spacetimedb tags: | type=ref,event=tag type=sha,prefix=commit-,suffix=-amd64 flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Cache Docker layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - name: Login to DockerHub uses: docker/login-action@v2 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Build and push uses: docker/build-push-action@v4 with: context: . file: crates/standalone/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new platforms: linux/amd64 - name: Merge images run: | ./tools/merge-docker-images.sh clockworklabs/spacetimedb "commit-${GITHUB_SHA:0:7}" "${GITHUB_SHA:0:7}-full" # This ugly bit is necessary if you don't want your cache to grow forever # until it hits GitHub's limit of 5GB. # Temp fix # https://github.com/docker/build-push-action/issues/252 # https://github.com/moby/buildkit/issues/1896 - name: Move cache run: | rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache docker-arm64: runs-on: arm-runner name: Build DockerHub ARM64 Container steps: - name: Install jq run: sudo apt-get install jq -y - name: Prune stale references run: git remote prune origin - name: Checkout uses: actions/checkout@v3 - name: Docker meta id: meta uses: docker/metadata-action@v4 with: images: | clockworklabs/spacetimedb tags: | type=ref,event=tag type=sha,prefix=commit-,suffix=-arm64 flavor: | latest=false - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Cache Docker layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - name: Login to DockerHub uses: docker/login-action@v2 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Build and push uses: docker/build-push-action@v4 with: context: . file: crates/standalone/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new platforms: linux/arm64/v8 - name: Merge images run: | ./tools/merge-docker-images.sh clockworklabs/spacetimedb "commit-${GITHUB_SHA:0:7}" "${GITHUB_SHA:0:7}-full" # This ugly bit is necessary if you don't want your cache to grow forever # until it hits GitHub's limit of 5GB. # Temp fix # https://github.com/docker/build-push-action/issues/252 # https://github.com/moby/buildkit/issues/1896 - name: Move cache run: | rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache ================================================ FILE: .github/workflows/docs-publish.yaml ================================================ name: Docs / Publish permissions: contents: read on: push: branches: - docs/release jobs: build: runs-on: spacetimedb-new-runner-2 steps: - name: Checkout repository uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '22' - uses: pnpm/action-setup@v4 with: run_install: true - name: Get pnpm store directory working-directory: sdks/typescript shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies working-directory: docs run: pnpm install - name: Docusaurus build working-directory: docs run: pnpm build - name: Publish docs to S3 uses: shallwefootball/s3-upload-action@master with: aws_key_id: ${{ secrets.DOCS_AWS_KEY_ID }} aws_secret_access_key: ${{ secrets.DOCS_AWS_SECRET_ACCESS_KEY}} aws_bucket: spacetimedb-docs source_dir: docs/build destination_dir: 'docs' ================================================ FILE: .github/workflows/docs-test.yaml ================================================ name: Docs / Test permissions: contents: read on: pull_request: jobs: build: runs-on: spacetimedb-new-runner-2 steps: - name: Checkout repository uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '22' - uses: pnpm/action-setup@v4 with: run_install: true - name: Get pnpm store directory working-directory: sdks/typescript shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install dependencies working-directory: docs run: pnpm install - name: Docusaurus build working-directory: docs run: pnpm build ================================================ FILE: .github/workflows/llm-benchmark-update.yml ================================================ name: Update LLM benchmarks on: workflow_dispatch: inputs: pr_number: description: "Pull Request Number" required: true issue_comment: types: [created] # only run when the comment is first created permissions: contents: read pull-requests: write issues: write concurrency: group: >- llm-benchmark -${{ github.event_name == 'issue_comment' && github.event.issue.number || inputs.pr_number }} ${{ github.event_name == 'issue_comment' && !startsWith(github.event.comment.body, '/update-llm-benchmark') && '-unrelated-comment' }} cancel-in-progress: true jobs: update-llm-benchmark: # Runnable either with a comment that starts with /update-llm-benchmark # or by manually dispatching if: | (github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/update-llm-benchmark')) || (github.event_name == 'workflow_dispatch') runs-on: spacetimedb-new-runner container: image: localhost:5000/spacetimedb-ci:latest options: >- --privileged steps: # Here we install the spacetime CLI for faster execution of the tests # SpacetimeDB itself is not under test here, rather it's the docs. # If we want to change that it is possible to have the benchmark compile # SpacetimeDB from source. - name: Install spacetime CLI run: | curl -sSf https://install.spacetimedb.com | sh -s -- -y echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Load PR info id: pr uses: actions/github-script@v7 with: script: | let prNumber; if (context.eventName === 'issue_comment') { prNumber = context.payload.issue.number; } else if (context.eventName === 'workflow_dispatch') { const raw = context.payload.inputs?.pr_number; if (!raw || !/^\d+$/.test(raw)) { core.setFailed(`Invalid pr_number input: '${raw}'.`); return; } prNumber = Number(raw); } else { core.setFailed(`Unsupported event: ${context.eventName}`); return; } const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber, }); core.setOutput('number', String(prNumber)); core.setOutput('head_ref', pr.head.ref); core.setOutput('head_sha', pr.head.sha); core.setOutput('head_repo_full_name', pr.head.repo.full_name); core.setOutput('head_owner_type', pr.head.repo.owner.type); // "User"|"Organization" core.setOutput('maintainer_can_modify', String(pr.maintainer_can_modify)); # If this was kicked off by a comment, ensure that the commenter is # a collaborator on the repo. We don't want unprivileged users to run benchmarks. # Note that the workflow that will be run will be the one that is on the `master` # branch, NOT the one from the PR. This is important so that the PR author can't # sneak in an exfiltration exploit. - name: Check commenter permission if: github.event_name == 'issue_comment' uses: actions/github-script@v7 with: script: | const user = context.payload.comment.user.login; const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: user, }); const allowed = new Set(['admin', 'maintain', 'write', 'triage']); if (!allowed.has(data.permission)) { core.setFailed(`User ${user} has permission '${data.permission}', not allowed to run benchmarks.`); } # If the PR is from a fork, we need to be able to have GitHub actions commit back # to the forked repo, so that we can update the benchmark results. # In order to do this we need to ensure that the PR is configured to allow the maintainers # of the SpacetimeDB repo to commit back ot the fork. - name: Check fork pushability (and comment if not) if: steps.pr.outputs.head_repo_full_name != github.repository uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.pr.outputs.number }} HEAD_OWNER_TYPE: ${{ steps.pr.outputs.head_owner_type }} MAINTAINER_CAN_MODIFY: ${{ steps.pr.outputs.maintainer_can_modify }} with: script: | const issue_number = Number(process.env.PR_NUMBER); const headOwnerType = process.env.HEAD_OWNER_TYPE; const canModify = process.env.MAINTAINER_CAN_MODIFY === 'true'; if (headOwnerType === 'Organization') { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number, body: [ "I can’t push benchmark updates to this PR because it comes from an **organization-owned fork**.", "GitHub doesn’t allow granting upstream maintainers push permissions to org-owned forks.", "", "Options:", "- Reopen the PR from a **personal fork** with **Allow edits from maintainers** enabled, or", "- A maintainer can apply the benchmark update on an internal branch." ].join("\n"), }); core.setFailed("Org-owned fork PR is not pushable by maintainers."); return; } if (!canModify) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number, body: [ "I can’t push benchmark updates to this PR branch until you enable **Allow edits from maintainers**.", "Please check the box on the PR page, then re-comment `/update-llm-benchmark`.", "See https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork" ].join("\n"), }); core.setFailed("maintainer_can_modify is false; author must enable 'Allow edits from maintainers'."); } # Run the benchmark that is already checked into master to prevent # an exfiltration attack whereby the PR author tries to sneak in an exploit # and get a maintainer to run the modified benchmark without looking at the # PR first. This ensure that we only ever execute code that is checked into # master. - name: Checkout master (build/install tool from trusted code) uses: actions/checkout@v4 with: ref: master fetch-depth: 0 persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 # Ensure we use a user-writable .NET install (not /usr/share/dotnet), # so workload installs don't require sudo. - name: Setup .NET SDK uses: actions/setup-dotnet@v4 with: dotnet-version: "8.0.x" - name: Install WASI workload (wasi-experimental) env: DOTNET_MULTILEVEL_LOOKUP: "0" DOTNET_CLI_HOME: ${{ runner.temp }}/dotnet-home DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "1" run: | dotnet --info dotnet workload install wasi-experimental --skip-manifest-update --disable-parallel - name: Install llm-benchmark tool from master run: | cargo install --path tools/xtask-llm-benchmark --locked command -v llm_benchmark # Check out the repo on the branch, but ONLY use this code as data! # Never execute code that is on the PR branch. - name: Checkout PR head (branch) uses: actions/checkout@v4 with: repository: ${{ steps.pr.outputs.head_repo_full_name }} ref: ${{ steps.pr.outputs.head_sha }} fetch-depth: 0 persist-credentials: false # Run the benchmark against the PR using the installed tool from the # master branch. - name: Run benchmark (with provider keys) env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} # Prevent MSBuild node reuse issues that cause "Pipe is broken" errors # when running multiple dotnet publish commands in parallel. # See: https://github.com/dotnet/msbuild/issues/6657 MSBUILDDISABLENODEREUSE: "1" DOTNET_CLI_USE_MSBUILD_SERVER: "0" run: | llm_benchmark ci-quickfix llm_benchmark ci-check # Generate failure analysis if there are any failures - name: Generate failure analysis env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | llm_benchmark analyze -o docs/llms/docs-benchmark-analysis.md || true # Generate PR comment markdown (compares against master baseline) - name: Generate PR comment markdown run: | llm_benchmark ci-comment - name: Ensure only docs/llms changed run: | set -euo pipefail CHANGED="$(git diff --name-only)" if [ -z "$CHANGED" ]; then echo "No changes." exit 0 fi if echo "$CHANGED" | grep -qvE '^docs/llms/'; then echo "Benchmark produced changes outside docs/llms:" echo "$CHANGED" | grep -vE '^docs/llms/' exit 1 fi # Comment the benchmark results on the PR - name: Comment benchmark results on PR uses: actions/github-script@v7 env: PR_NUMBER: ${{ steps.pr.outputs.number }} with: github-token: ${{ secrets.CLOCKWORK_LABS_BOT_PAT }} script: | const fs = require('fs'); // Read the pre-generated comment markdown const commentPath = 'docs/llms/docs-benchmark-comment.md'; if (!fs.existsSync(commentPath)) { core.setFailed(`Comment file not found: ${commentPath}`); return; } let body = fs.readFileSync(commentPath, 'utf8'); // Check if failure analysis exists and append it const analysisPath = 'docs/llms/docs-benchmark-analysis.md'; if (fs.existsSync(analysisPath)) { const analysis = fs.readFileSync(analysisPath, 'utf8'); // Only include if there's meaningful content (not just "no failures") if (!analysis.includes('No failures found')) { body += `\n
\nFailure Analysis (click to expand)\n\n${analysis}\n
`; } } const issue_number = Number(process.env.PR_NUMBER); // Always post a new comment console.log(`Posting new comment on PR #${issue_number}...`); try { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number, body, }); console.log('Comment created successfully'); } catch (err) { console.error('Failed to post comment:', err.message); console.error('Full error:', JSON.stringify(err, null, 2)); throw err; } # The benchmarks only modify the docs/llms directory. # Commit the changes. - name: Commit changes run: | git config user.name "clockwork-labs-bot" git config user.email "clockwork-labs-bot@users.noreply.github.com" # Prefer staging only the benchmark output area (adjust as needed) git add docs/llms git diff --cached --quiet && exit 0 git commit -m "Update LLM benchmark results" # Here we use the https://github.com/clockwork-labs-bot user's # personal access token to commit back to the PR branch. This is necessary # if we want to be able to push back to external contributor forks. - name: Push back to PR branch (same repo or fork) env: GH_TOKEN: ${{ secrets.CLOCKWORK_LABS_BOT_PAT }} run: | git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ steps.pr.outputs.head_repo_full_name }}.git" # Fetch and rebase in case branch moved since workflow started (e.g., previous benchmark run) git fetch origin "${{ steps.pr.outputs.head_ref }}" if ! git rebase "origin/${{ steps.pr.outputs.head_ref }}"; then git rebase --abort echo "::error::Rebase failed due to conflicts. The PR branch may have been updated during the benchmark run. Please re-run /update-llm-benchmark." exit 1 fi git push origin "HEAD:${{ steps.pr.outputs.head_ref }}" ================================================ FILE: .github/workflows/package.yml ================================================ name: Package SpacetimeDB CLI on: push: tags: - '**' workflow_dispatch: permissions: contents: read jobs: build-cli: strategy: fail-fast: false matrix: include: # WARNING - do not upgrade this runner to 24.04 or the self hosted runners because it will break downloads for # anyone who uses a linux distro that doesn't have glibc >= GLIBC_2.38 - { name: x86_64 Linux, target: x86_64-unknown-linux-gnu, runner: ubuntu-22.04 } - { name: aarch64 Linux, target: aarch64-unknown-linux-gnu, runner: arm-runner } # Disabled because musl builds weren't working and we didn't want to investigate. See https://github.com/clockworklabs/SpacetimeDB/pull/2964. # - { name: x86_64 Linux musl, target: x86_64-unknown-linux-musl, runner: bare-metal, container: alpine } # FIXME: arm musl build. "JavaScript Actions in Alpine containers are only supported on x64 Linux runners" # - { name: aarch64 Linux musl, target: aarch64-unknown-linux-musl, runner: arm-runner } - { name: aarch64 macOS, target: aarch64-apple-darwin, runner: macos-latest } - { name: x86_64 macOS, target: x86_64-apple-darwin, runner: macos-latest } - { name: x86_64 Windows, target: x86_64-pc-windows-msvc, runner: windows-latest } name: Build CLI for ${{ matrix.name }} runs-on: ${{ matrix.runner }} steps: - name: Checkout uses: actions/checkout@v3 - name: Show arch run: uname -a - name: Install musl dependencies # TODO: Should we use `matrix.container == 'alpine'` instead of the `endsWith` check? if: endsWith(matrix.target, '-musl') run: apk add gcc g++ bash curl linux-headers perl git make - name: Install Rust uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Install rust target run: rustup target add ${{ matrix.target }} - name: Add signtool.exe to PATH if: ${{ runner.os == 'Windows' }} shell: pwsh run: | $root = "${env:ProgramFiles(x86)}\Windows Kits\10\bin" $signtool = Get-ChildItem $root -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } | Sort-Object FullName -Descending | Select-Object -First 1 if (-not $signtool) { throw "signtool.exe not found under $root" } "Found: $($signtool.FullName)" $dir = Split-Path $signtool.FullName Add-Content -Path $env:GITHUB_PATH -Value $dir - name: Write certificate file for signing if: ${{ runner.os == 'Windows' }} shell: powershell env: DIGICERT_CERT_B64: ${{ secrets.DIGICERT_CERT_B64 }} run: | [IO.File]::WriteAllBytes("digicert.pfx", [Convert]::FromBase64String($env:DIGICERT_CERT_B64)) - name: Compile run: | cargo build --release --target ${{ matrix.target }} -p spacetimedb-cli -p spacetimedb-standalone -p spacetimedb-update - name: Sign binaries for Windows # Disabled for now since the current flow isn't working. if: false #if: ${{ runner.os == 'Windows' }} shell: powershell env: DIGICERT_KEYPAIR_ALIAS: ${{ secrets.DIGICERT_KEYPAIR_ALIAS }} run: | $ErrorActionPreference = 'Stop' $targetDir = Join-Path $env:GITHUB_WORKSPACE 'target\x86_64-pc-windows-msvc\release' $certFile = Join-Path $env:GITHUB_WORKSPACE 'digicert.pfx' $signtool = Get-Command signtool.exe -ErrorAction Stop $files = @( (Join-Path $targetDir 'spacetimedb-update.exe'), (Join-Path $targetDir 'spacetimedb-cli.exe'), (Join-Path $targetDir 'spacetimedb-standalone.exe') ) foreach ($file in $files) { & $signtool.Path sign /f $certFile /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 $file & $signtool.Path verify /v /pa $file } - name: Package (unix) if: ${{ runner.os != 'Windows' }} shell: bash run: | mkdir build cd target/${{matrix.target}}/release cp spacetimedb-update ../../../build/spacetimedb-update-${{matrix.target}} tar -czf ../../../build/spacetime-${{matrix.target}}.tar.gz spacetimedb-{cli,standalone} - name: Package (windows) if: ${{ runner.os == 'Windows' }} shell: bash run: | mkdir build cd target/${{matrix.target}}/release cp spacetimedb-update.exe ../../../build/spacetimedb-update-${{matrix.target}}.exe 7z a ../../../build/spacetime-${{matrix.target}}.zip spacetimedb-cli.exe spacetimedb-standalone.exe - name: Extract branch name shell: bash run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT id: extract_branch - name: Upload to DO Spaces uses: shallwefootball/s3-upload-action@master with: aws_key_id: ${{ secrets.AWS_KEY_ID }} aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} aws_bucket: ${{ vars.AWS_BUCKET }} source_dir: build endpoint: https://nyc3.digitaloceanspaces.com destination_dir: ${{ steps.extract_branch.outputs.branch }} ================================================ FILE: .github/workflows/pr_approval_check.yml ================================================ name: Review Checks # SECURITY: This workflow uses pull_request_target so that it has write access to # set commit statuses on external (fork) PRs. pull_request_target runs in the # context of the base branch, which grants the GITHUB_TOKEN write permissions # that a regular pull_request event on a fork would not have. # # IMPORTANT: This workflow must NEVER check out, build, or execute code from the # PR branch. Doing so would allow a malicious fork to run arbitrary code with # write access to the repository. This workflow only reads PR metadata via the # GitHub API, which is safe. on: pull_request_target: types: [opened, synchronize, reopened] pull_request_review: types: [submitted, dismissed] merge_group: permissions: contents: read pull-requests: read statuses: write concurrency: group: pr-approval-check-${{ github.event.pull_request.number || github.sha }} cancel-in-progress: true jobs: publish-approval-status: name: Set approval status runs-on: ubuntu-latest # SECURITY: Do not add a checkout step to this job. See comment at the top of this file. steps: - name: Evaluate and publish approval status uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const contextName = "PR approval check"; let targetSha; let state; let description; if (context.eventName === "merge_group") { targetSha = process.env.GITHUB_SHA; state = "success"; description = "Merge group entry; approvals already satisfied"; } else { const pr = context.payload.pull_request; targetSha = pr.head.sha; if (pr.head.repo.fork) { state = "success"; description = "Skipped for external PR"; } else if (pr.user.login !== "clockwork-labs-bot") { state = "success"; description = "PR author is not clockwork-labs-bot"; } else { const result = await github.graphql( ` query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $number) { latestOpinionatedReviews(first: 100, writersOnly: true) { nodes { state author { login } } } } } } `, { owner: context.repo.owner, repo: context.repo.repo, number: pr.number, } ); const effectiveApprovers = result.repository.pullRequest.latestOpinionatedReviews.nodes .filter((review) => review.state === "APPROVED") .map((review) => review.author?.login) .filter(Boolean); core.info( `Latest effective approvers (${effectiveApprovers.length}): ${effectiveApprovers.join(", ")}` ); if (effectiveApprovers.length < 2) { state = "failure"; description = "PRs from clockwork-labs-bot require at least 2 approvals"; } else { state = "success"; description = "PR has the required number of approvals"; } } } core.info(`Publishing status ${state} for ${targetSha}: ${description}`); // We need to set a separate commit status for this, because it runs on both // pull_request and pull_request_review events. If we don't set an explicit context, // what happens is that there are sometimes two separate statuses on the same commit - // one from each event type. This leads to weird cases where one copy of the check is failed, // and the other is successful, and the failed one blocks the PR from merging. await github.rest.repos.createCommitStatus({ owner: context.repo.owner, repo: context.repo.repo, sha: targetSha, state, context: contextName, description, }); ================================================ FILE: .github/workflows/rust_matcher.json ================================================ { "problemMatcher": [ { "owner": "rust", "pattern": [ { "regexp": "^(warning|warn|error)(\\[(.*)\\])?: (.*)$", "severity": 1, "message": 4, "code": 3 }, { "regexp": "^([\\s->=]*(.*):(\\d*):(\\d*)|.*)$", "file": 2, "line": 3, "column": 4 } ] } ] } ================================================ FILE: .github/workflows/tag-release.yml ================================================ on: release: types: [published] jobs: on-release: name: Re-tag latest runs-on: ubuntu-latest steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to DockerHub uses: docker/login-action@v2 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Retag the image run: | VERSION=${GITHUB_REF#refs/*/} docker buildx imagetools create clockworklabs/spacetimedb:$VERSION --tag clockworklabs/spacetimedb:latest ================================================ FILE: .github/workflows/typescript-lint.yml ================================================ name: TypeScript - Lint on: pull_request: push: branches: - master merge_group: jobs: build: runs-on: spacetimedb-new-runner-2 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 - uses: pnpm/action-setup@v4 with: run_install: true - name: Get pnpm store directory shell: bash run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Lint run: pnpm lint ================================================ FILE: .github/workflows/typescript-test.yml ================================================ name: TypeScript - Tests on: push: branches: - master pull_request: merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || format('sha-{0}', github.sha) }} cancel-in-progress: true jobs: build-and-test: runs-on: spacetimedb-new-runner-2 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 - uses: pnpm/action-setup@v4 with: run_install: true - name: Get pnpm store directory shell: bash working-directory: crates/bindings-typescript run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Build module library and SDK working-directory: crates/bindings-typescript run: pnpm build - name: Run module library and SDK tests working-directory: crates/bindings-typescript run: pnpm test # - name: Extract SpacetimeDB branch name from file # id: extract-branch # run: | # # Define the path to the branch file # BRANCH_FILE=".github/spacetimedb-branch.txt" # # Default to master if file doesn't exist # if [ ! -f "$BRANCH_FILE" ]; then # echo "::notice::No SpacetimeDB branch file found, using 'master'" # echo "branch=master" >> $GITHUB_OUTPUT # exit 0 # fi # # Read and trim whitespace from the file # branch=$(cat "$BRANCH_FILE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') # # Fallback to master if empty # if [ -z "$branch" ]; then # echo "::warning::SpacetimeDB branch file is empty, using 'master'" # branch="master" # fi # echo "branch=$branch" >> $GITHUB_OUTPUT # echo "Using SpacetimeDB branch from file: $branch" - name: Install Rust toolchain uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: workspaces: ${{ github.workspace }} shared-key: spacetimedb # Let the main CI job save the cache since it builds the most things save-if: false prefix-key: v1 # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with # `cargo build --manifest-path` (which apparently build different dependency trees). # However, we've been unable to fix it so... /shrug - name: Check v8 outputs run: | find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean -p v8 || true cargo build -p v8 fi if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then echo "Could not find v8 output file librusty_v8.a; rebuilding manually." cargo clean --release -p v8 || true cargo build --release -p v8 fi - name: Install SpacetimeDB CLI from the local checkout run: | export CARGO_HOME="$HOME/.cargo" echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" cargo install --force --path crates/cli --locked --message-format=short cargo install --force --path crates/standalone --locked --message-format=short # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime # Clear any existing information spacetime server clear -y - name: Generate client bindings working-directory: templates/chat-react-ts run: | pnpm generate - name: Check for changes working-directory: templates/chat-react-ts run: | "${GITHUB_WORKSPACE}"/tools/check-diff.sh src/module_bindings || { echo "Error: Bindings are dirty. Please generate bindings again and commit them to this branch." exit 1 } # - name: Start SpacetimeDB # run: | # spacetime start & # disown # - name: Publish module to SpacetimeDB # working-directory: SpacetimeDB/templates/quickstart-chat-typescript/spacetimedb # run: | # spacetime logout && spacetime login --server-issued-login local # spacetime publish -s local quickstart-chat -c -y # - name: Publish module to SpacetimeDB # working-directory: SpacetimeDB/templates/quickstart-chat-typescript/spacetimedb # run: | # spacetime logs quickstart-chat - name: Check that quickstart-chat builds working-directory: templates/chat-react-ts run: pnpm build - name: Check that templates build working-directory: templates/ run: pnpm -r --filter "./**" run build - name: Check that subdirectories build working-directory: crates/bindings-typescript run: pnpm -r --filter "./**" run build # - name: Run quickstart-chat tests # working-directory: examples/quickstart-chat # run: pnpm test # # # Run this step always, even if the previous steps fail # - name: Print rows in the user table # if: always() # run: spacetime sql quickstart-chat "SELECT * FROM user" ================================================ FILE: .github/workflows/upgrade-version-check.yml ================================================ name: Upgrade Version Check on: pull_request: types: [opened, synchronize] merge_group: permissions: read-all jobs: version_upgrade_check: runs-on: spacetimedb-new-runner-2 steps: - name: Checkout uses: actions/checkout@v3 - uses: dsherret/rust-toolchain-file@v1 - name: Set default rust toolchain run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) # pnpm is required for regenerating the typescript bindings - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 20 - uses: pnpm/action-setup@v4 with: run_install: true - name: Verify that upgrade-version still works run: cargo bump-versions 123.456.789 --rust-and-cli --csharp --typescript --cpp --accept-snapshots - name: Show diff run: git diff HEAD ================================================ FILE: .gitignore ================================================ # Created by https://www.toptal.com/developers/gitignore/api/rust,node,visualstudiocode # Edit at https://www.toptal.com/developers/gitignore?templates=rust,node,visualstudiocode perf.data perf.data.old flamegraph.html flamegraphs/*.svg flamegraphs/flamegraph.folded ### MacOS ### .DS_Store ### Node ### # Logs packages/logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules jspm_packages # Snowpack dependency directory (https://snowpack.dev/) web_modules # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variable files .env .env.development.local .env.test.local .env.production.local .env.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # vuepress v2.x temp and cache directory .temp # Docusaurus cache and generated files .docusaurus # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* ### Node Patch ### # Serverless Webpack directories .webpack/ # Optional stylelint cache # SvelteKit build / generate output .svelte-kit ### Rust ### # Generated by Cargo # will have compiled files and executables debug/ target/ # for docker images touching host fs linux-cache/ linux-rustup/ linux-target/ # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb ### VisualStudioCode ### .vscode/* .vscode !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets # Local History for Visual Studio Code .history/ # Built Visual Studio Code Extensions *.vsix ### VisualStudioCode Patch ### # Ignore all local history of files .history .ionide # Support for Project snippet scope # End of https://www.toptal.com/developers/gitignore/api/rust,node,visualstudiocode # Added by cargo # # already existing elements were commented out /target #Cargo.lock __pycache__/ .test_out ## JetBrains .idea/ /protobuf cs-src/ crates/bench/spacetime.svg crates/bench/sqlite.svg .vs/ # .NET build artifacts **/obj/ **/bin/ # benchmark files out.json old.json new.json .history.txt # Keys *.pem .ok.sql # Intermediate file generated when updating the external client SDKs' WS format bindings crates/client-api-messages/ws_schema.json # Test data !crates/core/testdata/ # Temporary templates dir for CLI's init command /crates/cli/.templates # C++ build data modules/benchmarks-cpp/build/ modules/module-test-cpp/build/ modules/sdk-test-cpp/build/ modules/sdk-test-connect-disconnect-cpp/build/ modules/sdk-test-procedure-cpp/build/ modules/sdk-test-view-cpp/build/ # Symlinked output from `nix build` result # Python venv directories venv/ # Claude Code .claude/ # Windows null device artifact nul [Nn][Uu][Gg][Ee][Tt].[Cc][Oo][Nn][Ff][Ii][Gg] [Nn][Uu][Gg][Ee][Tt].[Cc][Oo][Nn][Ff][Ii][Gg].meta # csharp SDK packages /sdks/csharp/packages/ /sdks/csharp/packages.meta # AI agent config .codex .claude # Any local file *.local ================================================ FILE: .prettierignore ================================================ node_modules pnpm-lock.yaml dist target .github coverage ================================================ FILE: .prettierrc ================================================ { "tabWidth": 2, "useTabs": false, "semi": true, "singleQuote": true, "arrowParens": "avoid", "jsxSingleQuote": false, "trailingComma": "es5", "endOfLine": "auto", "printWidth": 80 } ================================================ FILE: .rustfmt.toml ================================================ max_width = 120 style_edition = "2021" edition = "2024" ================================================ FILE: Cargo.toml ================================================ [workspace] exclude = ["crates/smoketests/modules"] members = [ "crates/auth", "crates/bench", "crates/bindings-sys", "crates/bindings", "crates/bindings-macro", "crates/cli", "crates/client-api", "crates/client-api-messages", "crates/commitlog", "crates/core", "crates/data-structures", "crates/datastore", "crates/durability", "crates/execution", "crates/expr", "crates/guard", "crates/fs-utils", "crates/lib", "crates/metrics", "crates/paths", "crates/pg", "crates/physical-plan", "crates/primitives", "crates/query", "crates/sats", "crates/schema", "crates/smoketests", "sdks/rust", "sdks/unreal", "crates/snapshot", "crates/sqltest", "crates/sql-parser", "crates/standalone", "crates/subscription", "crates/table", "crates/testing", "crates/update", "modules/benchmarks", "modules/keynote-benchmarks", "modules/perf-test", "modules/module-test", "templates/basic-rs/spacetimedb", "templates/chat-console-rs/spacetimedb", "templates/keynote-2/spacetimedb-rust-client", "modules/sdk-test", "modules/sdk-test-connect-disconnect", "modules/sdk-test-procedure", "modules/sdk-test-view", "modules/sdk-test-view-pk", "modules/sdk-test-event-table", "sdks/rust/tests/test-client", "sdks/rust/tests/test-counter", "sdks/rust/tests/connect_disconnect_client", "sdks/rust/tests/procedure-client", "sdks/rust/tests/view-client", "sdks/rust/tests/view-pk-client", "sdks/rust/tests/event-table-client", "tools/ci", "tools/upgrade-version", "tools/license-check", "tools/replace-spacetimedb", "tools/generate-client-api", "tools/gen-bindings", "tools/xtask-llm-benchmark", "crates/bindings-typescript/test-app/server", "crates/bindings-typescript/test-react-router-app/server", "crates/query-builder", ] default-members = ["crates/cli", "crates/standalone", "crates/update"] # cargo feature graph resolver. v3 is default in edition2024 but workspace # manifests don't have editions. resolver = "3" [profile.release] opt-level = 3 debug-assertions = false overflow-checks = false lto = "thin" panic = 'unwind' incremental = false codegen-units = 16 rpath = false [profile.dev] opt-level = 0 debug = true debug-assertions = true overflow-checks = true lto = false panic = 'unwind' incremental = true codegen-units = 256 rpath = false [profile.bench] debug = true [profile.test] opt-level = 1 debug = true [profile.profiling] inherits = "release" debug = true [workspace.package] version = "2.0.5" edition = "2024" # update rust-toolchain.toml too! rust-version = "1.93.0" [workspace.dependencies] spacetimedb = { path = "crates/bindings", version = "=2.0.5" } spacetimedb-auth = { path = "crates/auth", version = "=2.0.5" } spacetimedb-bindings-macro = { path = "crates/bindings-macro", version = "=2.0.5" } spacetimedb-bindings-sys = { path = "crates/bindings-sys", version = "=2.0.5" } spacetimedb-cli = { path = "crates/cli", version = "=2.0.5" } spacetimedb-client-api = { path = "crates/client-api", version = "=2.0.5" } spacetimedb-client-api-messages = { path = "crates/client-api-messages", version = "=2.0.5" } spacetimedb-codegen = { path = "crates/codegen", version = "=2.0.5" } spacetimedb-commitlog = { path = "crates/commitlog", version = "=2.0.5" } spacetimedb-core = { path = "crates/core", version = "=2.0.5" } spacetimedb-data-structures = { path = "crates/data-structures", version = "=2.0.5" } spacetimedb-datastore = { path = "crates/datastore", version = "=2.0.5" } spacetimedb-durability = { path = "crates/durability", version = "=2.0.5" } spacetimedb-execution = { path = "crates/execution", version = "=2.0.5" } spacetimedb-expr = { path = "crates/expr", version = "=2.0.5" } spacetimedb-guard = { path = "crates/guard", version = "=2.0.5" } spacetimedb-lib = { path = "crates/lib", default-features = false, version = "=2.0.5" } spacetimedb-memory-usage = { path = "crates/memory-usage", version = "=2.0.5", default-features = false } spacetimedb-metrics = { path = "crates/metrics", version = "=2.0.5" } spacetimedb-paths = { path = "crates/paths", version = "=2.0.5" } spacetimedb-pg = { path = "crates/pg", version = "=2.0.5" } spacetimedb-physical-plan = { path = "crates/physical-plan", version = "=2.0.5" } spacetimedb-primitives = { path = "crates/primitives", version = "=2.0.5" } spacetimedb-query = { path = "crates/query", version = "=2.0.5" } spacetimedb-sats = { path = "crates/sats", version = "=2.0.5" } spacetimedb-schema = { path = "crates/schema", version = "=2.0.5" } spacetimedb-standalone = { path = "crates/standalone", version = "=2.0.5" } spacetimedb-sql-parser = { path = "crates/sql-parser", version = "=2.0.5" } spacetimedb-table = { path = "crates/table", version = "=2.0.5" } spacetimedb-fs-utils = { path = "crates/fs-utils", version = "=2.0.5" } spacetimedb-snapshot = { path = "crates/snapshot", version = "=2.0.5" } spacetimedb-subscription = { path = "crates/subscription", version = "=2.0.5" } spacetimedb-query-builder = { path = "crates/query-builder", version = "=2.0.5" } # Prevent `ahash` from pulling in `getrandom` by disabling default features. # Modules use `getrandom02` and we need to prevent an incompatible version # from appearing in module dependency graphs. ahash = { version = "0.8", default-features = false, features = ["std"] } anyhow = "1.0.68" anymap = "0.12" arrayvec = "0.7.2" async-stream = "0.3.6" async-trait = "0.1.68" axum = { version = "0.7", features = ["tracing"] } axum-extra = { version = "0.9", features = ["typed-header"] } backtrace = "0.3.66" base64 = "0.21.2" bigdecimal = "0.4.7" bitflags = "2.3.3" blake3 = "1.5.1" brotli = "3.5" byte-unit = "4.0.18" bytemuck = { version = "1.16.2", features = ["must_cast"] } bytes = "1.10.1" bytestring = { version = "1.2.0", features = ["serde"] } cargo_metadata = "0.17.0" chrono = { version = "0.4.24", default-features = false } clap = { version = "4.2.4", features = ["derive", "wrap_help"] } clap-markdown = "0.1.4" colored = "2.0.0" console = { version = "0.15.6" } convert_case = "0.6.0" crc32c = "0.6.4" criterion = { version = "0.5.1", features = ["async", "async_tokio", "html_reports"] } crossbeam-channel = "0.5" crossbeam-queue = "0.3.12" cursive = { version = "0.20", default-features = false, features = ["crossterm-backend"] } decorum = { version = "0.3.1", default-features = false, features = ["std"] } derive_more = "0.99" dialoguer = { version = "0.11", default-features = false } dirs = "5.0.1" duct = "0.13.5" either = "1.9" email_address = "0.2.4" enum-as-inner = "0.6" enum-map = "2.6.3" env_logger = "0.10" ethnum = { version = "1.5.0", features = ["serde"] } flate2 = "1.0.24" flume = { version = "0.11", default-features = false, features = ["async"] } foldhash = "0.2.0" fs-err = "2.9.0" fs_extra = "1.3.0" fs2 = "0.4.3" futures = "0.3" futures-channel = "0.3" futures-util = "0.3" getrandom02 = { package = "getrandom", version = "0.2" } git2 = "0.19" glob = "0.3.1" hashbrown = { version = "0.16.1", default-features = false, features = ["equivalent", "inline-more", "rayon", "serde"] } headers = "0.4" heck = "0.4" hex = "0.4.3" home = "0.5" hostname = "^0.3" http = "1.0" http-body-util= "0.1.3" humantime = "2.3" hyper = "1.0" hyper-util = { version = "0.1", features = ["tokio"] } ignore = "0.4" imara-diff = "0.1.3" indexmap = "2.0.0" indicatif = "0.17" insta = { version = "1.21.0", features = ["toml", "filters"] } is-terminal = "0.4" itertools = "0.12" itoa = "1" json5 = "0.4" jsonwebtoken = { package = "spacetimedb-jsonwebtoken", version = "9.3.0" } junction = "1" jwks = { package = "spacetimedb-jwks", version = "0.1.3" } lazy_static = "1.4.0" lean_string = "0.5.1" log = "0.4.17" memchr = "2" mimalloc = "0.1.39" names = "0.14" netstat2 = "0.11" nohash-hasher = "0.2" notify = "7.0" nix = "0.30" once_cell = "1.16" parking_lot = { version = "0.12.1", features = ["send_guard", "arc_lock"] } parse-size = "1.1.0" paste = "1.0" percent-encoding = "2.3" petgraph = { version = "0.6.5", default-features = false } pin-project-lite = "0.2.9" pgwire = { version = "0.37", default-features = false, features = ["server-api", "pg-ext-types"] } postgres-types = "0.2.5" pretty_assertions = { version = "1.4", features = ["unstable"] } proc-macro2 = "1.0" prometheus = "0.14.0" proptest = "1.4" proptest-derive = "0.5" quick-junit = { version = "0.3.2" } quick-xml = "0.31" quote = "1.0.8" rand08 = { package = "rand", version = "0.8" } rand = "0.9" rand_distr = "0.5.1" rayon = "1.8" rayon-core = "1.11.0" regex = "1" reqwest = { version = "0.12", features = ["stream", "json"] } rolldown = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } rolldown_common = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } rolldown_error = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } rolldown_utils = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" } ron = "0.8" rusqlite = { version = "0.29.0", features = ["bundled", "column_decltype"] } rust_decimal = { version = "1.29.1", features = ["db-tokio-postgres"] } rustc-demangle = "0.1.21" rustc-hash = "2" rustyline = { version = "12.0.0", features = [] } scoped-tls = "1.0.1" scopeguard = "1.1.0" second-stack = "0.3" self-replace = "1.5" semver = "1" serde = { version = "1.0.136", features = ["derive"] } serde_json = { version = "1.0.128", features = ["raw_value", "arbitrary_precision"] } serde_path_to_error = "0.1.9" serde_with = { version = "3.3.0", features = ["base64", "hex"] } serial_test = "2.0.0" sha1 = "0.10.1" sha3 = "0.10.0" similar = "2.3" slab = "0.4.7" sled = "0.34.7" smallvec = { version = "1.11", features = ["union", "const_generics"] } socket2 = "0.5" sourcemap = "9.3.1" sqllogictest = "0.17" sqllogictest-engines = "0.17" sqlparser = "0.38.0" strum = { version = "0.25.0", features = ["derive"] } syn = { version = "2", features = ["full", "extra-traits"] } syntect = { version = "5.0.0", default-features = false, features = ["default-fancy"] } tabled = "0.14.0" tar = "0.4" tempdir = "0.3.7" tempfile = "3.20" termcolor = "1.2.0" termtree = "0.5.1" thin-vec = "0.2.13" thiserror = "1.0.37" tokio = { version = "1.37", features = ["full"] } tokio_metrics = { version = "0.4.0" } tokio-postgres = { version = "0.7.8", features = ["with-chrono-0_4"] } tokio-stream = "0.1.17" tokio-tungstenite = { version = "0.27.0", features = ["native-tls", "url"] } tokio-util = { version = "0.7.4", features = ["time"] } toml = "0.8" toml_edit = "0.22.22" tower-http = { version = "0.5", features = ["cors"] } tower-layer = "0.3" tower-service = "0.3" tracing = "0.1.37" tracing-appender = "0.2.2" tracing-core = "0.1.31" tracing-flame = "0.2.0" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } trybuild = "1" typed-arena = "2.0" unicode-ident = "1.0.12" unicode-normalization = "0.1.23" url = "2.3.1" urlencoding = "2.1.2" uuid = { version = "1.18.1", default-features = false } # Pinned to a specific version rather than allowing SemVer fuzzy resolution # because our flake references the specific version of this library. # See librusty_v8.nix for more details. # When updating the V8 crate, either update the version and hashes in that file, # or ping phoebe @gefjon to do so. v8 = "=145.0.0" # This version is kept in sync with the version of ICU required by v8. # When that changes, the `v8::icu::set_common_data_XX()` function is renamed. deno_core_icudata = "0.77" walkdir = "2.2.5" wasmbin = "0.6" webbrowser = "1.0.2" windows-sys = "0.59" xdg = "2.5" xmltree = "0.11" tikv-jemallocator = { version = "0.6.0", features = ["profiling", "stats"] } tikv-jemalloc-ctl = { version = "0.6.0", features = ["stats"] } jemalloc_pprof = { version = "0.8", features = ["symbolize", "flamegraph"] } zstd-framed = { version = "0.1.1", features = ["tokio"] } # Vendor the openssl we rely on, rather than depend on a # potentially very old system version. openssl = { version = "0.10", features = ["vendored"] } [workspace.dependencies.wasmtime] version = "39" default-features = false features = [ "addr2line", "async", "cache", "cranelift", "demangle", "parallel-compilation", "runtime", "std", ] [workspace.dependencies.wasmtime-internal-fiber] version = "39" default-features = false features = ["std"] [workspace.dependencies.tracing-tracy] version = "0.10.4" # We use the "ondemand" feature to allow connecting after the start, # and reconnecting, from the tracy client to the database. # TODO(George): Need to be able to remove "broadcast" in some build configurations. features = [ "enable", "system-tracing", "context-switch-tracing", "sampling", "code-transfer", "broadcast", "ondemand", ] [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] } [workspace.lints.clippy] # FIXME: we should work on this lint incrementally result_large_err = "allow" [workspace.metadata] # This silences a warning in our Nix flake, which otherwise would be upset that our call to `crateNameFromCargoToml` # is running against a file that doesn't have a name in it. crane.name = "spacetimedb" ================================================ FILE: Dockerfile ================================================ # Use a base image that supports multi-arch FROM rust:bookworm AS builder WORKDIR /usr/src/app COPY . . # If we're in a git submodule, we'll have a corrupted/nonfunctional .git file instead of a proper .git directory. # To make the errors more sane, remove .git entirely. RUN if [ -f .git ]; then \ echo "❌ ERROR: .git is a file (likely a submodule pointer), not a directory." >&2; \ echo "This will cause errors in the build process, because git operations will fail." >&2; \ echo "To address this, replace the .git file with a proper .git directory." >&2; \ exit 1; \ fi RUN cargo build -p spacetimedb-standalone -p spacetimedb-cli --release --locked FROM rust:bookworm # Install dependencies RUN apt-get update && apt-get install -y \ curl \ ca-certificates \ binaryen \ build-essential \ && rm -rf /var/lib/apt/lists/* # Determine architecture for .NET installation ARG TARGETARCH ENV DOTNET_ARCH=${TARGETARCH} RUN if [ "$DOTNET_ARCH" = "amd64" ]; then \ DOTNET_ARCH="x64"; \ elif [ "$DOTNET_ARCH" = "arm64" ]; then \ DOTNET_ARCH="arm64"; \ else \ echo "Unsupported architecture: $DOTNET_ARCH" && exit 1; \ fi && \ curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 8.0 --install-dir /usr/share/dotnet --architecture $DOTNET_ARCH ENV PATH="/usr/share/dotnet:${PATH}" # Install the experimental WASI workload RUN dotnet workload install wasi-experimental # Install Rust WASM target RUN rustup target add wasm32-unknown-unknown # Copy over SpacetimeDB COPY --from=builder --chmod=755 /usr/src/app/target/release/spacetimedb-standalone /usr/src/app/target/release/spacetimedb-cli /opt/spacetime/ RUN ln -s /opt/spacetime/spacetimedb-cli /usr/local/bin/spacetime # Create and switch to a non-root user RUN useradd -m spacetime USER spacetime # Set working directory WORKDIR /app # Expose the necessary port EXPOSE 3000 # Define the entrypoint ENTRYPOINT ["spacetime"] ================================================ FILE: LICENSE.txt ================================================ SPACETIMEDB BUSINESS SOURCE LICENSE AGREEMENT Business Source License 1.1 Parameters Licensor: Clockwork Laboratories, Inc. Licensed Work: SpacetimeDB 2.0.5 The Licensed Work is (c) 2023 Clockwork Laboratories, Inc. Additional Use Grant: You may make use of the Licensed Work provided your application or service uses the Licensed Work with no more than one SpacetimeDB instance in production and provided that you do not use the Licensed Work for a Database Service. A “Database Service” is a commercial offering that allows third parties (other than your employees and contractors) to access the functionality of the Licensed Work by creating tables whose schemas are controlled by such third parties. Change Date: 2031-03-12 Change License: GNU Affero General Public License v3.0 with a linking exception For information about alternative licensing arrangements for the Software, please visit: https://spacetimedb.com Notice The Business Source License (this document, or the “License”) is not an Open Source license. However, the Licensed Work will eventually be made available under an Open Source License, as stated in this License. License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. “Business Source License” is a trademark of MariaDB Corporation Ab. ----------------------------------------------------------------------------- Base License and Subdirectory Specific Licenses 1. Repository-Wide License Except as provided in Section 2 below, the contents of this repository are licensed under the Business Source License (“BSL”), which includes a change date resulting in a licensing change to the GNU Affero General Public License v3.0 with Linking Exception on that date. See the full text of the BSL and AGPL with Linking Exception in this file below. 2. Subdirectory-Specific Licenses Certain subdirectories within this repository are licensed under different terms. If a subdirectory contains its own LICENSE or LICENSE.txt file, the terms in that file apply exclusively to all files and subfolders within that subdirectory. In the event of any conflict between this base license and a subdirectory’s license, the base license will govern for that subdirectory’s contents. 3. Contributor Acknowledgement By contributing to this repository, you agree that: Your contributions will be licensed under the license applicable to the directory or subdirectory in which your contribution is made. If you contribute to multiple subdirectories, the applicable license for each subdirectory will apply to your contributions in that subdirectory. 4. Reading the Applicable License Before using, modifying, or distributing code from this repository, you must read: This base LICENSE.txt file for the overall repository license. Any LICENSE or LICENSE.txt file in a subdirectory that you intend to use or contribute to. ----------------------------------------------------------------------------- Business Source License 1.1 Terms The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work. The Licensor may make an Additional Use Grant, above, permitting limited production use. Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate. If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work. All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License. This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor. You must conspicuously display this License on each original or modified copy of the Licensed Work. If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work. Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work. This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License). TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this License’s text to license your works, and to refer to it using the trademark “Business Source License”, as long as you comply with the Covenants of Licensor below. Covenants of Licensor In consideration of the right to use this License’s text and the “Business Source License” name and trademark, Licensor covenants to MariaDB, and to all other recipients of the licensed work to be provided by Licensor: 1. To specify as the Change License the GPL Version 2.0 or any later version, or a license that is compatible with GPL Version 2.0 or a later version, where “compatible” means that software provided under the Change License can be included in a program with software provided under GPL Version 2.0 or a later version. Licensor may specify additional Change Licenses without limitation. 2. To either: (a) specify an additional grant of rights to use that does not impose any additional restriction on the right granted in this License, as the Additional Use Grant; or (b) insert the text “None”. 3. To specify a Change Date. 4. Not to modify this License in any other way. ----------------------------------------------------------------------------- Copyright (C) 2023 Clockwork Laboratories, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License, version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see . Additional permission under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with SpacetimeDB (or a modified version of that library), containing parts covered by the terms of the AGPL v3.0, the licensors of this Program grant you additional permission to convey the resulting work. Additional permission under GNU AGPL version 3 section 13 If you modify this Program, or any covered work, by linking or combining it with SpacetimeDB (or a modified version of that library), containing parts covered by the terms of the AGPL v3.0, the licensors of this Program grant you additional permission that, notwithstanding any other provision of this License, you need not prominently offer all users interacting with your modified version remotely through a computer network an opportunity to receive the Corresponding Source of your version from a network server at no charge, if your version supports such interaction. This permission does not waive or modify any other obligations or terms of the AGPL v3.0, except for the specific requirement set forth in section 13. A copy of the AGPL v3.0 license is reproduced below. GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright © 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. SpacetimeDB: A database which replaces your server. Copyright (C) 2023 Clockwork Laboratories, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: README.md ================================================

SpacetimeDB Logo SpacetimeDB Logo

SpacetimeDB SpacetimeDB

Development at the speed of light.

         

   

     

Discord   Twitter   GitHub   Twitch   YouTube   LinkedIn   StackOverflow


## What is SpacetimeDB? SpacetimeDB is a relational database that is also a server. You upload your application logic directly into the database, and clients connect to it without any server in between. Write your schema and business logic as a **module** in [Rust](https://spacetimedb.com/docs/quickstarts/rust), [C#](https://spacetimedb.com/docs/quickstarts/c-sharp), [TypeScript](https://spacetimedb.com/docs/quickstarts/typescript), or [C++](https://spacetimedb.com/docs/quickstarts/c-plus-plus). SpacetimeDB compiles it, runs it inside the database, and automatically synchronizes state to connected clients in real-time. Instead of deploying a web or game server that sits in between your clients and your database, your clients connect directly to the database and execute your application logic in your module. You can write all of your permission and authorization logic right inside your module just as you would in a normal server. This means that you can write your entire application in a single language and deploy it as a single binary. No more separate webserver, no more containers, no more Kubernetes, no more VMs, no more DevOps, no more caching later. Zero infrastructure to manage.
SpacetimeDB Architecture

SpacetimeDB application architecture
(elements in white are provided by SpacetimeDB)

SpacetimeDB is optimized for maximum speed and minimum latency. SpacetimeDB provides all the ACID guarantees of a traditional RDBMS, with all the speed of an optimized web server. All application state is held in memory for fast access, while a commit log on disk provides durability and crash recovery. The entire backend of our MMORPG [BitCraft Online](https://bitcraftonline.com) runs as a single SpacetimeDB module: chat, items, terrain, player positions, everything, synchronized to thousands of players in real-time. ## Quick Start ### 1. Install ```bash # macOS / Linux curl -sSf https://install.spacetimedb.com | sh # Windows (PowerShell) iwr https://windows.spacetimedb.com -useb | iex ``` ### 2. Log in ```bash spacetime login ``` This opens a browser to authenticate with GitHub. Your identity is linked to your account so you can publish databases. ### 3. Start developing ```bash spacetime dev --template chat-react-ts ``` That is it. This creates a project from a template, publishes it to [Maincloud](https://spacetimedb.com/docs/how-to/deploy/maincloud), and watches for file changes, automatically rebuilding and republishing on save. See [pricing](https://spacetimedb.com/pricing) for details. ## How It Works SpacetimeDB modules define **tables** (your data) and **reducers** (your logic). Clients connect, call reducers, and subscribe to tables. When data changes, SpacetimeDB pushes updates to subscribed clients automatically. ```rust // Define a table #[spacetimedb::table(accessor = messages, public)] pub struct Message { #[primary_key] #[auto_inc] id: u64, sender: Identity, text: String, } // Define a reducer (your API endpoint) #[spacetimedb::reducer] pub fn send_message(ctx: &ReducerContext, text: String) { ctx.db.messages().insert(Message { id: 0, sender: ctx.sender, text, }); } ``` On the client side, subscribe and get live updates: ```typescript const [messages] = useTable(tables.message); // messages updates automatically when the server state changes. // No polling. No refetching. ``` ## Language Support ### Server Modules Write your database logic in any of these languages: | Language | Quickstart | |----------|-----------| | **Rust** | [Get started](https://spacetimedb.com/docs/quickstarts/rust) | | **C#** | [Get started](https://spacetimedb.com/docs/quickstarts/c-sharp) | | **TypeScript** | [Get started](https://spacetimedb.com/docs/quickstarts/typescript) | | **C++** | [Get started](https://spacetimedb.com/docs/quickstarts/c-plus-plus) | ### Client SDKs Connect from any of these platforms: | SDK | Quickstart | |-----|-----------| | **TypeScript** (React, Next.js, Vue, Svelte, Angular, Node.js, Bun, Deno) | [Get started](https://spacetimedb.com/docs/quickstarts/react) | | **Rust** | [Get started](https://spacetimedb.com/docs/quickstarts/rust) | | **C#** (standalone and Unity) | [Get started](https://spacetimedb.com/docs/quickstarts/c-sharp) | | **C++** (Unreal Engine) | [Get started](https://spacetimedb.com/docs/quickstarts/c-plus-plus) | ## Running with Docker ```bash docker run --rm --pull always -p 3000:3000 clockworklabs/spacetime start ``` ## Building from Source If you need features from `master` that have not been released yet: ```bash # Prerequisites: Rust toolchain with wasm32-unknown-unknown target curl https://sh.rustup.rs -sSf | sh git clone https://github.com/clockworklabs/SpacetimeDB cd SpacetimeDB cargo build --locked --release -p spacetimedb-standalone -p spacetimedb-update -p spacetimedb-cli ``` Then install the binaries:
macOS / Linux ```bash mkdir -p ~/.local/bin STDB_VERSION="$(./target/release/spacetimedb-cli --version | sed -n 's/.*spacetimedb tool version \([0-9.]*\);.*/\1/p')" mkdir -p ~/.local/share/spacetime/bin/$STDB_VERSION cp target/release/spacetimedb-update ~/.local/bin/spacetime cp target/release/spacetimedb-cli ~/.local/share/spacetime/bin/$STDB_VERSION cp target/release/spacetimedb-standalone ~/.local/share/spacetime/bin/$STDB_VERSION # Add to your shell config if not already present: export PATH="$HOME/.local/bin:$PATH" # Set the active version: spacetime version use $STDB_VERSION ```
Windows (PowerShell) ```powershell $stdbDir = "$HOME\AppData\Local\SpacetimeDB" $stdbVersion = & ".\target\release\spacetimedb-cli" --version | Select-String -Pattern 'spacetimedb tool version ([0-9.]+);' | ForEach-Object { $_.Matches.Groups[1].Value } New-Item -ItemType Directory -Path "$stdbDir\bin\$stdbVersion" -Force | Out-Null Copy-Item "target\release\spacetimedb-update.exe" "$stdbDir\spacetime.exe" Copy-Item "target\release\spacetimedb-cli.exe" "$stdbDir\bin\$stdbVersion\" Copy-Item "target\release\spacetimedb-standalone.exe" "$stdbDir\bin\$stdbVersion\" # Add to your system PATH: %USERPROFILE%\AppData\Local\SpacetimeDB # Then in a new shell: spacetime version use $stdbVersion ```
Verify with `spacetime --version`. ## Documentation Full documentation is available at **[spacetimedb.com/docs](https://spacetimedb.com/docs)**, including: - [Quickstart guides](https://spacetimedb.com/docs) for every supported language and framework - [Core concepts](https://spacetimedb.com/docs/core-concepts): tables, reducers, subscriptions, authentication - [Tutorials](https://spacetimedb.com/docs/tutorials/chat-app): chat app, Unity multiplayer, Unreal Engine multiplayer - [Deployment guide](https://spacetimedb.com/docs/how-to/deploy/maincloud): publishing to Maincloud - [CLI reference](https://spacetimedb.com/docs/reference/cli-reference) - [SQL reference](https://spacetimedb.com/docs/reference/sql-reference) ## License SpacetimeDB is licensed under the [Business Source License 1.1 (BSL)](LICENSE.txt). It converts to the AGPL v3.0 with a linking exception after a few years. The linking exception means you are **not** required to open-source your own code if you use SpacetimeDB. You only need to contribute back changes to SpacetimeDB itself. **Why did we choose this license?** We chose to license SpacetimeDB under the MariaDB Business Source License for 4 years because we can't compete with AWS while also building our products for them. We chose GPLv3 with linking exception as the open source license because we want contributions merged back into mainline (just like Linux), but we don't want to make anyone else open source their own code (i.e. linking exception). ================================================ FILE: clippy.toml ================================================ disallowed-macros = [ { path = "std::print", reason = "print blocks on a global mutex for synchronization, and its output cannot be filtered. Use a log macro instead, or apply #[allow(disallowed-macros)] if this is test or CLI code." }, { path = "std::println", reason = "println blocks on a global mutex for synchronization, and its output cannot be filtered. Use a log macro instead, or apply #[allow(disallowed-macros)] if this is test or CLI code." }, { path = "std::eprint", reason = "eprint blocks on a global mutex for synchronization, and its output cannot be filtered. Use a log macro instead, or apply #[allow(disallowed-macros)] if this is test or CLI code." }, { path = "std::eprintln", reason = "eprintln blocks on a global mutex for synchronization, and its output cannot be filtered. Use a log macro instead, or apply #[allow(disallowed-macros)] if this is test or CLI code." }, { path = "std::dbg", reason = "dbg is a debugging tool and should never be committed into the repository." }, ] ================================================ FILE: crates/auth/Cargo.toml ================================================ [package] name = "spacetimedb-auth" version.workspace = true edition.workspace = true rust-version.workspace = true license-file = "LICENSE" description = "Auth helpers for SpacetimeDB" [dependencies] spacetimedb-data-structures.workspace = true spacetimedb-lib = { workspace = true, features = ["serde"] } anyhow.workspace = true serde.workspace = true serde_json.workspace = true serde_with.workspace = true jsonwebtoken.workspace = true [dev-dependencies] serde_json.workspace = true [lints] workspace = true ================================================ FILE: crates/auth/src/identity.rs ================================================ pub use jsonwebtoken::errors::Error as JwtError; pub use jsonwebtoken::errors::ErrorKind as JwtErrorKind; pub use jsonwebtoken::{DecodingKey, EncodingKey}; use serde::Deserializer; use serde::{Deserialize, Serialize}; use spacetimedb_data_structures::map::HashMap; use spacetimedb_lib::Identity; use std::time::SystemTime; #[derive(Debug, Clone)] pub struct ConnectionAuthCtx { pub claims: SpacetimeIdentityClaims, pub jwt_payload: Box, } impl TryFrom for ConnectionAuthCtx { type Error = anyhow::Error; fn try_from(claims: SpacetimeIdentityClaims) -> Result { let payload = serde_json::to_string(&claims).map_err(|e| anyhow::anyhow!("Failed to serialize claims: {e}"))?; Ok(ConnectionAuthCtx { claims, jwt_payload: payload.into(), }) } } // These are the claims that can be attached to a request/connection. #[serde_with::serde_as] #[derive(Debug, Serialize, Deserialize, Clone)] pub struct SpacetimeIdentityClaims { #[serde(rename = "hex_identity")] pub identity: Identity, #[serde(rename = "sub")] pub subject: Box, #[serde(rename = "iss")] pub issuer: Box, #[serde(rename = "aud")] pub audience: Box<[Box]>, /// The unix timestamp the token was issued at #[serde_as(as = "serde_with::TimestampSeconds")] pub iat: SystemTime, #[serde_as(as = "Option")] pub exp: Option, #[serde(flatten)] pub extra: Option, serde_json::Value>>, } fn deserialize_audience<'de, D>(deserializer: D) -> Result]>, D::Error> where D: Deserializer<'de>, { // By using `untagged`, it will try the different options. #[derive(Deserialize)] #[serde(untagged)] enum Audience { Single(Box), Multiple(Box<[Box]>), } // Deserialize into the enum let audience = Audience::deserialize(deserializer)?; // Convert the enum into a list. Ok(match audience { Audience::Single(s) => [s].into(), Audience::Multiple(v) => v, }) } // IncomingClaims are from the token we receive from the client. // The signature should be verified already, but further validation is needed to have a SpacetimeIdentityClaims2. #[serde_with::serde_as] #[derive(Debug, Serialize, Deserialize)] pub struct IncomingClaims { #[serde(rename = "hex_identity")] pub identity: Option, #[serde(rename = "sub")] pub subject: Box, #[serde(rename = "iss")] pub issuer: Box, #[serde(rename = "aud", default, deserialize_with = "deserialize_audience")] pub audience: Box<[Box]>, /// The unix timestamp the token was issued at #[serde_as(as = "serde_with::TimestampSeconds")] pub iat: SystemTime, #[serde_as(as = "Option")] pub exp: Option, /// All remaining claims from the JWT payload #[serde(flatten)] pub extra: Option, serde_json::Value>>, } impl TryInto for IncomingClaims { type Error = anyhow::Error; fn try_into(self) -> anyhow::Result { // The issuer and subject must be less than 128 bytes. if self.issuer.len() > 128 { return Err(anyhow::anyhow!("Issuer too long: {:?}", self.issuer)); } if self.subject.len() > 128 { return Err(anyhow::anyhow!("Subject too long: {:?}", self.subject)); } // The issuer and subject must be non-empty. if self.issuer.is_empty() { return Err(anyhow::anyhow!("Issuer empty")); } if self.subject.is_empty() { return Err(anyhow::anyhow!("Subject empty")); } let computed_identity = Identity::from_claims(&self.issuer, &self.subject); // If an identity is provided, it must match the computed identity. if let Some(token_identity) = self.identity && token_identity != computed_identity { return Err(anyhow::anyhow!( "Identity mismatch: token identity {token_identity:?} does not match computed identity {computed_identity:?}", )); } Ok(SpacetimeIdentityClaims { identity: computed_identity, subject: self.subject, issuer: self.issuer, audience: self.audience, iat: self.iat, exp: self.exp, extra: self.extra, }) } } #[cfg(test)] mod tests { use super::*; use serde_json::json; use std::time::UNIX_EPOCH; #[test] fn test_deserialize_audience_single_string() { let json_data = json!({ "sub": "123", "iss": "example.com", "aud": "audience1", "iat": 1693425600, "exp": 1693512000 }); let claims: IncomingClaims = serde_json::from_value(json_data).unwrap(); assert_eq!(claims.audience, ["audience1".into()].into()); assert_eq!(&*claims.subject, "123"); assert_eq!(&*claims.issuer, "example.com"); assert_eq!(claims.iat, UNIX_EPOCH + std::time::Duration::from_secs(1693425600)); assert_eq!( claims.exp, Some(UNIX_EPOCH + std::time::Duration::from_secs(1693512000)) ); } #[test] fn test_deserialize_audience_multiple_strings() { let json_data = json!({ "sub": "123", "iss": "example.com", "aud": ["audience1", "audience2"], "iat": 1693425600, "exp": 1693512000 }); let claims: IncomingClaims = serde_json::from_value(json_data).unwrap(); assert_eq!(claims.audience, ["audience1".into(), "audience2".into()].into()); assert_eq!(&*claims.subject, "123"); assert_eq!(&*claims.issuer, "example.com"); assert_eq!(claims.iat, UNIX_EPOCH + std::time::Duration::from_secs(1693425600)); assert_eq!( claims.exp, Some(UNIX_EPOCH + std::time::Duration::from_secs(1693512000)) ); } #[test] fn test_deserialize_audience_missing_field() { let json_data = json!({ "sub": "123", "iss": "example.com", "iat": 1693425600, "exp": 1693512000 }); let claims: IncomingClaims = serde_json::from_value(json_data).unwrap(); assert!(claims.audience.is_empty()); // Since `default` is used, it should be an empty vector assert_eq!(&*claims.subject, "123"); assert_eq!(&*claims.issuer, "example.com"); assert_eq!(claims.iat, UNIX_EPOCH + std::time::Duration::from_secs(1693425600)); assert_eq!( claims.exp, Some(UNIX_EPOCH + std::time::Duration::from_secs(1693512000)) ); } } ================================================ FILE: crates/auth/src/lib.rs ================================================ pub mod identity; ================================================ FILE: crates/bench/.gitignore ================================================ .spacetime/ target/ ================================================ FILE: crates/bench/Cargo.toml ================================================ [package] name = "spacetimedb-bench" version.workspace = true edition.workspace = true license-file = "LICENSE" description = "Bench library/utility for SpacetimeDB" publish = false [[bench]] name = "special" harness = false [[bench]] name = "generic" harness = false [[bench]] name = "callgrind" harness = false [[bench]] name = "subscription" harness = false [[bench]] name = "delete_table" harness = false [[bench]] name = "index" harness = false [[bin]] name = "summarize" [lib] bench = false [dependencies] spacetimedb-client-api = { path = "../client-api" } spacetimedb-client-api-messages = { path = "../client-api-messages" } spacetimedb-core = { path = "../core", features = ["test"] } spacetimedb-data-structures.workspace = true spacetimedb-datastore.workspace = true spacetimedb-execution = { path = "../execution" } spacetimedb-lib = { path = "../lib" } spacetimedb-paths.workspace = true spacetimedb-primitives = { path = "../primitives" } spacetimedb-query = { path = "../query" } spacetimedb-sats = { path = "../sats" } spacetimedb-schema = { workspace = true, features = ["test"] } spacetimedb-standalone = { path = "../standalone" } spacetimedb-table = { path = "../table" } spacetimedb-testing = { path = "../testing" } ahash.workspace = true anyhow.workspace = true convert_case.workspace = true anymap.workspace = true byte-unit.workspace = true clap.workspace = true criterion.workspace = true futures.workspace = true foldhash.workspace = true hashbrown.workspace = true lazy_static.workspace = true log.workspace = true mimalloc.workspace = true rand.workspace = true regex.workspace = true rusqlite.workspace = true serde.workspace = true serde_json.workspace = true serial_test.workspace = true smallvec.workspace = true tempdir.workspace = true tokio.workspace = true tracing-subscriber.workspace = true walkdir.workspace = true itertools.workspace = true [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = { workspace = true } tikv-jemalloc-ctl = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] # only try to build these on linux # also: # we've forked iai-callgrind to add custom entrypoint support. # FIXME(jgilles): revert to depending on the crates version if we ever get that upstreamed. # iai-callgrind = "0.7.2" iai-callgrind = { git = "https://github.com/clockworklabs/iai-callgrind.git", branch = "main" } iai-callgrind-runner = { git = "https://github.com/clockworklabs/iai-callgrind.git", branch = "main" } iai-callgrind-macros = { git = "https://github.com/clockworklabs/iai-callgrind.git", branch = "main" } [lints] workspace = true ================================================ FILE: crates/bench/Dockerfile ================================================ # Dockerfile for callgrind benchmarking environment. # Set up to run from linux / WSL (running from a windows file system will be extremely slow). # See the README for commands to run. # sync with: ../../rust-toolchain.toml FROM rust:1.93.0 RUN apt-get update && \ apt-get install -y valgrind bash && \ rm -rf /var/lib/apt/lists/* ENV CARGO_TARGET_DIR=/projects/SpacetimeDB/linux-target ENV CARGO_HOME=/projects/SpacetimeDB/linux-cache ENV RUSTUP_HOME=/projects/SpacetimeDB/linux-rustup RUN cargo install --git https://github.com/clockworklabs/iai-callgrind.git --branch main iai-callgrind-runner ================================================ FILE: crates/bench/README.md ================================================ # spacetimedb-bench > ⚠️ **Internal Crate** ⚠️ > > This crate is intended for internal use only. It is **not** stable and may change without notice. Benchmarking suite for SpacetimeDB using [Criterion](https://github.com/bheisler/criterion.rs) and [Callgrind](https://valgrind.org/docs/manual/cl-manual.html) (via [iai-callgrind](https://github.com/clockworklabs/iai-callgrind)). Provides comparisons between the underlying spacetime datastore, spacetime modules, and sqlite. To run the criterion benchmarks: ```bash cargo bench --bench generic --bench special ``` To enable the criterion benchmarks that do one million inserts or updates, set the RUN_ONE_MILLION environment variable: ```bash RUN_ONE_MILLION=true cargo bench --bench generic --bench special ``` To run the callgrind benchmarks, you need valgrind installed. The easiest way to get it is to use the docker image in this folder. There's a handy bash script: ```bash bash callgrind-docker.sh ``` Which will build the docker image and run the callgrind benchmarks inside of it. You can also comment "benchmarks please" or "callgrind please" on a pull request in the SpacetimeDB repository to run the criterion/callgrind benchmarks on that PR. The results will be posted in a comment on the PR. This is coordinated using the benchmarks GitHub Actions: see [`../../.github/workflows/benchmarks.yml`](../../.github/workflows/benchmarks.yml), and [`../../.github/workflows/callgrind_benchmarks.yml`](../../.github/workflows/callgrind_benchmarks.yml). These also rely on the benchmarks-viewer application (https://github.com/clockworklabs/benchmarks-viewer). ## Caveats The criterion benchmarks take a long time to run -- there are a lot of them. See below for information on running select groups of them. The callgrind benchmarks only measure a select portion of the codebase. In particular, they do not collect instruction counts in any async code, due to callgrind limitations; they only time the synchronous code that runs a reducer / the client code that calls a reducer. This is most of the code that it takes to run a reducer, including all database modifications. The async code between client and reducer is mostly just handing off data through a couple of channels. Still, if you're interested in measuring that code, you should rely on the criterion benchmarks & `perf`. ## Criterion benchmarks Timings for spacetime modules should be understood as *latencies to call a spacetime reducer without network delays*. They include serialization and deserialization times for any arguments passed. There are also separate benchmarks that measure these times on their own. The complete structure (not yet fully implemented) is as follows: ``` # generic benchmarks: [db]/[disk]/ empty_transaction/ insert_1/[schema]/[index_type]/[load] insert_bulk/[schema]/[index_type]/[load]/[count] insert_large_value/[count] iterate/[schema]/[count]/ filter/[string, u64]/[index?]/[load]/[count] find_unique/u32/[load]/ delete/[index_type]/[load]/[count] # "load" refers to the number of rows in the table at the start of the benchmark. # db: [stdb_raw, stdb_module, sqlite] # disk: [disk, mem] # schema: [person, location] # index_type: [unique, non_unique, multi_index] # "person" is a schema with 2 ints and a string, "location" is a schema with 3 ints. serialize/ bsatn/[schema]/[count] json/[schema]/[count] product_value/[schema]/[count] deserialize/ bsatn/[schema]/[count] json/[schema]/[count] stdb_module/ print_bulk/[count] large_arguments/64KiB/ db_game/ circles/load=[num rows] ia_loop/load=[num rows] ``` Typically, you don't want to run all benchmarks at once, there are a lot of them and it will take many minutes. You can pass regexes to the bench script to select what slice of benchmarks you'd like. For example, ```sh cargo bench -- 'stdb_raw/.*/insert_bulk' ``` Will run all of the `insert_bulk` benchmarks against the raw spacetime backend. Similarly, ```sh cargo bench -- 'mem/.*/unique' ``` Will run benchmarks involving unique primary keys against all databases, without writing to disc. ## Workload Benches The workload benches within the `db_game` module are designed to simulate realistic workloads commonly found in games. Instead of testing for a small set of values, these benches generate a larger number of rows to more effectively stress the database engine. As consequence, they take more time when run inside `criterion`. This approach reduces interference caused by noise and provides the potential for better detection of display improvements or regressions in performance. To run the workload benches directly outside `criterion`, you can use the following commands: ```bash cargo test --release --package spacetimedb-testing --test standalone_integration_test test_calling_bench_db_ia_loop -- --exact --nocapture cargo test --release --package spacetimedb-testing --test standalone_integration_test test_calling_bench_db_circles -- --exact --nocapture ``` ## Pretty report To generate a nicely formatted markdown report, you can use the "summarize" binary. This is used on CI (see [`../../.github/workflows/benchmarks.yml`](../../.github/workflows/benchmarks.yml)). To generate a report without comparisons, use: ```bash cargo bench --bench generic --bench special -- --save-baseline current cargo run --bin summarize markdown-report current ``` To compare to another branch, do: ```bash git checkout master cargo bench --bench generic --bench special -- --save-baseline base git checkout high-octane-feature-branch cargo bench --bench generic --bench special -- --save-baseline current cargo run --bin summarize markdown-report current base ``` Of course, this will take about an hour, so it might be better to let the CI do it for you. ## Adding more There are two ways to write benchmarks: - Targeted, non-generic benchmarks (`benches/special.rs`) - Generic over database backends (`benches/generic.rs`) See the following sections for how to write these. #### Targeted benchmarks These are regular [Criterion.rs](https://github.com/bheisler/criterion.rs) benchmarks. Nothing fancy, do whatever you like with these. Put them in `benches/special.rs`. #### Generic benchmarks Generic benchmarks live in `benches/generic.rs`. These benchmarks involve two traits: - [`BenchDatabase`](src/database.rs), which is implemented by different database backends. - [`BenchTable`](src/schemas.rs), which is implemented by different benchmark tables. To add a new generic benchmark, you'll need to: - Add a relevant method to the `BenchDatabase` trait. - Implement it for each `BenchDatabase` implementation. - [`SQLite`](src/sqlite.rs) will require you to write it as a SQL query and submit that to sqlite. - [`SpacetimeRaw`](src/spacetime_raw.rs) will require you to execute the query against the Spacetime database backend directly. - [`SpacetimeModule`](src/spacetime_module.rs) will require you to add a reducer to the [`benchmarks`](../../modules/benchmarks/src/lib.rs) crate, and then add logic to invoke your reducer with the correct arguments. - Add a benchmark harness that actually invokes your method in `benches/generic.rs`. ## Callgrind benchmarks These live in `benches/callgrind.rs` and can only be run on linux, although a docker image is provided above. See that f ## Install tools There are also some scripts that rely on external tools to extract data from the benchmarks. ### OSX + Linux - [samply](https://github.com/mstange/samply/) ```bash cargo install samply ``` Run *any* command to see perf data on Firefox: ```bash # Note if the `cargo` command triggers compilation, it will also be captured in the profile. # Therefore it is useful to run this after the artifact has already been cached. samply record -r 10000000 cargo bench --bench=subscription --profile=profiling -- full-scan --exact --profile-time=30 ``` ### OSX Only - [cargo-instrument](https://github.com/cmyr/cargo-instruments) ```bash brew install cargo-instruments ``` ```bash # ./instruments.sh TEMPLATE BENCHMARK BENCHMARK_FILTER ./instruments.sh time generic stdb_raw/mem/insert_bulk/location/multi_index/load=0/count=100 ``` Where `TEMPLATE` is one from ```bash cargo instruments --list-templates ``` ### Linux only - [cargo-flamegraph](https://github.com/flamegraph-rs/flamegraph) ```bash cargo install flamegraph ``` You can generate flamegraphs using `flamegraph.sh` script: ```bash # ./flamegraph.sh BENCH_EXECUTABLE FILTER SVG_PATH ./flamegraph.sh generic stdb_raw/mem/insert_bulk/location/multi_index/load=0/count=100 result.svg" ``` ================================================ FILE: crates/bench/benches/callgrind.rs ================================================ #[cfg(target_os = "linux")] mod callgrind_benches { /// Benchmarks that run under our iai-callgrind fork (https://github.com/clockworklabs/iai-callgrind) /// Callgrind (https://valgrind.org/docs/manual/cl-manual.html) disassembles linux binaries /// as they run to insert instrumentation code. This allows for benchmarking with minimal variance, /// compared to timing benchmarks. /// /// Note: you CAN'T save state between these benchmarks, since each is run /// in a fresh process. /// /// FIXME(jgilles): many of the spacetime_module benchmarks are currently disabled due to a bad interaction with sled (issue #564). /// /// There is some odd boilerplate in this file that is used to make downstream processing of these benchmarks easier. /// Every benchmark is a struct that implements serde::Deserialize; these structs contain some redundant information. /// Benchmarks are actually dispatched using iai-callgrind's `#[library_benchmark]` macro. /// We pass benchmark configurations as JSON strings to these benchmarks. /// This JSON ends up in iai-callgrind's output file; we parse all relevant information out of it. /// (In the `crate/src/bin/summarize.rs` binary.) use iai_callgrind::{library_benchmark, library_benchmark_group, LibraryBenchmarkConfig}; use serde::Deserialize; use spacetimedb_bench::{ database::BenchDatabase, schemas::{create_partly_identical, create_sequential, u32_u64_str, BenchTable, IndexStrategy, RandomTable}, spacetime_raw::SpacetimeRaw, sqlite::SQLite, }; use spacetimedb_lib::{AlgebraicType, ProductValue}; /// A benchmark. /// /// The benchmark information consists of metadata that can be deserialized from JSON. /// The JSON is embedded in the output generated by iai-callgrind, which /// makes writing downstream tools a lot easier. /// // TODO(jgilles): use this infra in the non-callgrind benches as well. trait Benchmark: Deserialize<'static> { fn run_benchmark(self); } // ========================= INSERT BULK ========================= #[derive(Deserialize)] struct InsertBulkBenchmark { bench: String, db: String, in_memory: bool, schema: String, indices: IndexStrategy, preload: u32, count: u32, #[serde(skip)] _marker: std::marker::PhantomData<(DB, T)>, } impl Benchmark for InsertBulkBenchmark { fn run_benchmark(self) { assert_eq!(self.bench, "insert bulk", "provided metadata has incorrect bench name"); assert_eq!(self.db, DB::name(), "provided metadata has incorrect db name"); assert_eq!(self.schema, T::name(), "provided metadata has incorrect db name"); let mut db = DB::build(self.in_memory).unwrap(); let table_id = db.create_table::(self.indices).unwrap(); let mut data = create_sequential::(0xdeadbeef, self.count + self.preload, 64); let to_preload = data.split_off(self.count as usize); // warm up db.insert_bulk(&table_id, data.clone()).unwrap(); db.clear_table(&table_id).unwrap(); // add preload data db.insert_bulk(&table_id, to_preload).unwrap(); // measure spacetimedb::callgrind_flag::enable_callgrind_globally(|| { db.insert_bulk(&table_id, data).unwrap(); }); // clean up db.clear_table(&table_id).unwrap(); } } #[library_benchmark] #[bench::mem_unique_64(r#"{"bench":"insert bulk", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::mem_btree_each_column_64(r#"{"bench":"insert bulk", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "btree_each_column", "preload": 128, "count": 64}"#)] #[bench::disk_unique_64(r#"{"bench":"insert bulk", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::disk_btree_each_column_64(r#"{"bench":"insert bulk", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "btree_each_column", "preload": 128, "count": 64}"#)] fn insert_bulk_raw_u32_u64_str(metadata: &str) { let bench: InsertBulkBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } #[library_benchmark] #[bench::mem_unique_64(r#"{"bench":"insert bulk", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::mem_btree_each_column_64(r#"{"bench":"insert bulk", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "btree_each_column", "preload": 128, "count": 64}"#)] #[bench::disk_unique_64(r#"{"bench":"insert bulk", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::disk_btree_each_column_64(r#"{"bench":"insert bulk", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "btree_each_column", "preload": 128, "count": 64}"#)] fn insert_bulk_sqlite_u32_u64_str(metadata: &str) { let bench: InsertBulkBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } library_benchmark_group!( name = insert_bulk_group; benchmarks = insert_bulk_raw_u32_u64_str, /*insert_bulk_module_u32_u64_str,*/ insert_bulk_sqlite_u32_u64_str ); // ========================= UPDATE BULK ========================= #[derive(Deserialize)] struct UpdateBulkBenchmark { bench: String, db: String, in_memory: bool, schema: String, indices: IndexStrategy, preload: u32, count: u32, #[serde(skip)] _marker: std::marker::PhantomData<(DB, T)>, } impl Benchmark for UpdateBulkBenchmark { fn run_benchmark(self) { assert_eq!(self.bench, "update bulk", "provided metadata has incorrect bench name"); assert_eq!(self.db, DB::name(), "provided metadata has incorrect db name"); assert_eq!(self.schema, T::name(), "provided metadata has incorrect db name"); assert_eq!( self.indices, IndexStrategy::Unique0, "provided metadata has incorrect index strategy" ); let mut db = DB::build(self.in_memory).unwrap(); let table_id = db.create_table::(self.indices).unwrap(); let data = create_sequential::(0xdeadbeef, self.preload, 64); // warm up db.insert_bulk(&table_id, data).unwrap(); db.update_bulk::(&table_id, self.count).unwrap(); // measure spacetimedb::callgrind_flag::enable_callgrind_globally(|| { db.update_bulk::(&table_id, self.count).unwrap(); }); // clean up db.clear_table(&table_id).unwrap(); } } #[library_benchmark] #[bench::mem_64(r#"{"bench":"update bulk", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::mem_1024(r#"{"bench":"update bulk", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "preload": 1024, "count": 1024}"#)] #[bench::disk_64(r#"{"bench":"update bulk", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::disk_1024(r#"{"bench":"update bulk", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "preload": 1024, "count": 1024}"#)] fn update_bulk_raw_u32_u64_str(metadata: &str) { let bench: UpdateBulkBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } #[library_benchmark] #[bench::mem_64(r#"{"bench":"update bulk", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::mem_1024(r#"{"bench":"update bulk", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "preload": 1024, "count": 1024}"#)] #[bench::disk_64(r#"{"bench":"update bulk", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "preload": 128, "count": 64}"#)] #[bench::disk_1024(r#"{"bench":"update bulk", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "preload": 1024, "count": 1024}"#)] fn update_bulk_sqlite_u32_u64_str(metadata: &str) { let bench: UpdateBulkBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } library_benchmark_group!( name = update_bulk_group; benchmarks = update_bulk_raw_u32_u64_str, /*update_bulk_module_u32_u64_str,*/ update_bulk_sqlite_u32_u64_str ); // ========================= ITERATE ========================= #[derive(Deserialize)] struct IterateBenchmark { bench: String, db: String, in_memory: bool, schema: String, indices: IndexStrategy, count: u32, #[serde(skip)] _marker: std::marker::PhantomData<(DB, T)>, } impl Benchmark for IterateBenchmark { fn run_benchmark(self) { assert_eq!(self.bench, "iterate", "provided metadata has incorrect bench name"); assert_eq!(self.db, DB::name(), "provided metadata has incorrect db name"); assert_eq!(self.schema, T::name(), "provided metadata has incorrect db name"); let mut db = DB::build(self.in_memory).unwrap(); let table_id = db.create_table::(self.indices).unwrap(); let data = create_sequential::(0xdeadbeef, self.count, 64); // warm up db.insert_bulk(&table_id, data).unwrap(); db.iterate(&table_id).unwrap(); // measure spacetimedb::callgrind_flag::enable_callgrind_globally(|| { db.iterate(&table_id).unwrap(); }); // clean up db.clear_table(&table_id).unwrap(); } } #[library_benchmark] #[bench::mem_64( r#"{"bench": "iterate", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "count": 64}"# )] #[bench::mem_1024( r#"{"bench": "iterate", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "count": 1024}"# )] #[bench::disk_64( r#"{"bench": "iterate", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "count": 64}"# )] #[bench::disk_1024( r#"{"bench": "iterate", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "count": 1024}"# )] fn iterate_raw_u32_u64_str(metadata: &str) { let bench: IterateBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } #[library_benchmark] #[bench::mem_64( r#"{"bench": "iterate", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "count": 64}"# )] #[bench::mem_1024( r#"{"bench": "iterate", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "unique_0", "count": 1024}"# )] #[bench::disk_64( r#"{"bench": "iterate", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "count": 64}"# )] #[bench::disk_1024( r#"{"bench": "iterate", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "unique_0", "count": 1024}"# )] fn iterate_sqlite_u32_u64_str(metadata: &str) { let bench: IterateBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } library_benchmark_group!( name = iterate_group; benchmarks = iterate_raw_u32_u64_str, /*iterate_module_u32_u64_str,*/ iterate_sqlite_u32_u64_str ); // ========================= FILTER ========================= #[derive(Deserialize)] struct FilterBenchmark { bench: String, db: String, in_memory: bool, schema: String, indices: IndexStrategy, count: u32, preload: u32, // Underscore here cause it's an implementation detail. // The only thing downstream cares about is data_type _column: u16, data_type: String, #[serde(skip)] _marker: std::marker::PhantomData<(DB, T)>, } impl Benchmark for FilterBenchmark { fn run_benchmark(self) { assert_eq!(self.bench, "filter", "provided metadata has incorrect bench name"); assert_eq!(self.db, DB::name(), "provided metadata has incorrect db name"); assert_eq!(self.schema, T::name(), "provided metadata has incorrect db name"); let filter_column_type = match T::product_type().elements[self._column as usize].algebraic_type { AlgebraicType::String => "string", AlgebraicType::U32 => "u32", AlgebraicType::U64 => "u64", _ => unimplemented!(), }; assert_eq!( filter_column_type, self.data_type, "provided metadata has incorrect data type" ); let mut db = DB::build(self.in_memory).unwrap(); let table_id = db.create_table::(self.indices).unwrap(); let data = create_partly_identical::(0xdeadbeef, self.count as u64, self.preload as u64); // create_partly_identical guarantees that the first `count` elements will be identical let filter_value = data[0].clone().into_product_value().elements[self._column as usize].clone(); // warm up db.insert_bulk(&table_id, data).unwrap(); db.filter::(&table_id, self._column, filter_value.clone()).unwrap(); // measure spacetimedb::callgrind_flag::enable_callgrind_globally(|| { db.filter::(&table_id, self._column, filter_value.clone()).unwrap(); }); // clean up db.clear_table(&table_id).unwrap(); } } #[library_benchmark] #[bench::string_mem_no_index_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] #[bench::string_mem_btree_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] #[bench::u64_mem_no_index_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] #[bench::u64_mem_btree_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": true, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] #[bench::string_disk_no_index_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] #[bench::string_disk_btree_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] #[bench::u64_disk_no_index_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] #[bench::u64_disk_btree_64_from_128( r#"{"bench": "filter", "db": "stdb_raw", "in_memory": false, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] fn filter_raw_u32_u64_str(metadata: &str) { let bench: FilterBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } #[library_benchmark] // string, btree index #[bench::string_mem_no_index_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] // string, btree index #[bench::string_mem_btree_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] // u64, no index #[bench::u64_mem_no_index_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] // u64, btree index #[bench::u64_mem_btree_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": true, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] // string, no index #[bench::string_disk_no_index_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] // string, btree index #[bench::string_disk_btree_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 2, "data_type": "string"}"# )] // u64, no index #[bench::u64_disk_no_index_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "no_index", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] // u64, btree index #[bench::u64_disk_btree_64_from_128( r#"{"bench": "filter", "db": "sqlite", "in_memory": false, "schema": "u32_u64_str", "indices": "btree_each_column", "count": 64, "preload": 128, "_column": 1, "data_type": "u64"}"# )] fn filter_sqlite_u32_u64_str(metadata: &str) { let bench: FilterBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } library_benchmark_group!( name = filter_group; benchmarks = filter_raw_u32_u64_str, /*filter_module_u32_u64_str,*/ filter_sqlite_u32_u64_str ); // ========================= FIND ========================= #[derive(Deserialize)] #[expect(unused)] // TODO struct FindBenchmark { bench: String, db: String, schema: String, indices: IndexStrategy, preload: u32, #[serde(skip)] _marker: std::marker::PhantomData<(DB, T)>, } impl Benchmark for FindBenchmark { fn run_benchmark(self) { assert_eq!(self.bench, "find", "provided metadata has incorrect bench name"); assert_eq!(self.db, DB::name(), "provided metadata has incorrect db name"); assert_eq!(self.schema, T::name(), "provided metadata has incorrect db name"); assert_eq!( T::product_type().elements[0].algebraic_type, AlgebraicType::U32, "primary key in tuple slot 0 must be u32" ); let mut db = DB::build(false).unwrap(); let table_id = db.create_table::(self.indices).unwrap(); let data = create_sequential::(0xdeadbeef, self.preload, 64); let filter_value = data[(self.preload - 1) as usize].clone().into_product_value().elements[0].clone(); // warm up db.insert_bulk(&table_id, data).unwrap(); db.filter::(&table_id, 0, filter_value.clone()).unwrap(); // measure spacetimedb::callgrind_flag::enable_callgrind_globally(|| { db.filter::(&table_id, 0, filter_value.clone()).unwrap(); }); // clean up db.clear_table(&table_id).unwrap(); } } // ========================= EMPTY TRANSACTION ========================= #[derive(Deserialize)] struct EmptyTransactionBenchmark { bench: String, db: String, in_memory: bool, #[serde(skip)] _marker: std::marker::PhantomData, } impl Benchmark for EmptyTransactionBenchmark { fn run_benchmark(self) { assert_eq!( self.bench, "empty transaction", "provided metadata has incorrect bench name" ); assert_eq!(self.db, DB::name(), "provided metadata has incorrect db name"); let mut db = DB::build(self.in_memory).unwrap(); // warm up db.empty_transaction().unwrap(); // measure spacetimedb::callgrind_flag::enable_callgrind_globally(|| db.empty_transaction().unwrap()); } } #[library_benchmark] #[bench::empty_in_mem(r#"{"bench": "empty transaction", "db": "stdb_raw", "in_memory": true}"#)] #[bench::empty_on_disk(r#"{"bench": "empty transaction", "db": "stdb_raw", "in_memory": false}"#)] fn empty_transaction_raw(metadata: &str) { let bench: EmptyTransactionBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } /* #[library_benchmark] #[bench::b1(r#"{"bench": "empty transaction", "db": "stdb_module"}"#)] fn empty_transaction_module(metadata: &str) { let bench: EmptyTransactionBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } */ #[library_benchmark] #[bench::empty_in_mem(r#"{"bench": "empty transaction", "db": "sqlite", "in_memory": true}"#)] #[bench::empty_on_disk(r#"{"bench": "empty transaction", "db": "sqlite", "in_memory": false}"#)] fn empty_transaction_sqlite(metadata: &str) { let bench: EmptyTransactionBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } library_benchmark_group!( name = empty_transaction_group; benchmarks = empty_transaction_raw, /*empty_transaction_module,*/ empty_transaction_sqlite ); // ========================= SERIALIZATION ========================= #[derive(Deserialize)] struct BSatnSerializationBenchmark { bench: String, format: String, count: u32, } impl Benchmark for BSatnSerializationBenchmark { fn run_benchmark(self) { assert_eq!( self.bench, "serialize_product_value", "provided metadata has incorrect bench name" ); assert_eq!(self.format, "bsatn", "provided metadata has incorrect format"); let buckets = 64; let data = create_sequential::(0xdeadbeef, self.count, buckets) .into_iter() .map(|row| spacetimedb_lib::AlgebraicValue::Product(row.into_product_value())) .collect::(); spacetimedb::callgrind_flag::enable_callgrind_globally(|| { // don't time deallocation: return this! spacetimedb_lib::sats::bsatn::to_vec(&data).unwrap() }); // allocation dropped here } } #[library_benchmark] #[bench::b1(r#"{"bench": "serialize_product_value", "format": "bsatn", "count": 16}"#)] #[bench::b2(r#"{"bench": "serialize_product_value", "format": "bsatn", "count": 64}"#)] fn bsatn_serialization(metadata: &str) { let bench: BSatnSerializationBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } #[derive(Deserialize)] struct JSONSerializationBenchmark { bench: String, format: String, count: u32, } impl Benchmark for JSONSerializationBenchmark { fn run_benchmark(self) { assert_eq!( self.bench, "serialize_product_value", "provided metadata has incorrect bench name" ); assert_eq!(self.format, "json", "provided metadata has incorrect format"); let buckets = 64; let data = create_sequential::(0xdeadbeef, self.count, buckets) .into_iter() .map(|row| spacetimedb_lib::AlgebraicValue::Product(row.into_product_value())) .collect::(); spacetimedb::callgrind_flag::enable_callgrind_globally(|| { // don't time deallocation: return this! serde_json::to_string(&data).unwrap() }); // allocation dropped here } } #[library_benchmark] #[bench::b1(r#"{"bench": "serialize_product_value", "format": "json", "count": 16}"#)] #[bench::b2(r#"{"bench": "serialize_product_value", "format": "json", "count": 64}"#)] fn json_serialization(metadata: &str) { let bench: JSONSerializationBenchmark = serde_json::from_str(metadata).unwrap(); bench.run_benchmark(); } library_benchmark_group!( name = serialize_group; benchmarks = bsatn_serialization, json_serialization ); // ========================= HARNESS ========================= iai_callgrind::main!( config = LibraryBenchmarkConfig::default() .pass_through_envs(["HOME", "PATH", "RUST_LOG", "RUST_BACKTRACE"]) // THE NEXT LINE IS CRITICAL. // Without this line, this entire file breaks! .with_custom_entry_point("spacetimedb::callgrind_flag::flag"); library_benchmark_groups = insert_bulk_group, update_bulk_group, filter_group, iterate_group, empty_transaction_group, serialize_group ); // have to re-export `main`, it's not marked as `pub` in the macro pub fn run_benches() { main(); } } fn main() { #[cfg(target_os = "linux")] callgrind_benches::run_benches(); #[cfg(not(target_os = "linux"))] println!("Callgrind does not exist for your operating system."); } ================================================ FILE: crates/bench/benches/delete_table.rs ================================================ use core::{cmp::Ordering, iter::repeat_with, time::Duration}; use criterion::{ black_box, criterion_group, criterion_main, measurement::{Measurement as _, WallTime}, BenchmarkGroup, Criterion, }; use itertools::Itertools; use rand::{prelude::*, seq::SliceRandom}; use smallvec::SmallVec; use spacetimedb_data_structures::map::HashSet; use spacetimedb_datastore::locking_tx_datastore::delete_table; use spacetimedb_sats::layout::Size; use spacetimedb_table::indexes::{PageIndex, PageOffset, RowPointer, SquashedOffset}; use std::collections::BTreeSet; fn time(body: impl FnOnce() -> R) -> Duration { let start = WallTime.start(); let ret = body(); let end = WallTime.end(start); black_box(ret); end } const FIXED_ROW_SIZE: Size = Size(4 * 4); fn gen_row_pointers(iters: u64) -> impl Iterator { let mut page_index = PageIndex(0); let mut page_offset = PageOffset(0); let iter = repeat_with(move || { page_offset += FIXED_ROW_SIZE; if page_offset >= PageOffset::PAGE_END { // Consumed the page, let's use a new page. page_index.0 += 1; page_offset = PageOffset(0); } black_box(RowPointer::new( false, page_index, page_offset, SquashedOffset::COMMITTED_STATE, )) }); iter.take(iters as usize) } fn bench_custom(g: &mut BenchmarkGroup<'_, WallTime>, name: &str, run: impl Fn(u64) -> Duration) { g.bench_function(name, |b| b.iter_custom(&run)); } fn bench_delete_table(c: &mut Criterion) { let name = DT::NAME; let mut g = c.benchmark_group(name); let row_size = black_box(FIXED_ROW_SIZE); let new_dt = || DT::new(row_size); bench_custom(&mut g, "mixed", |i| { let mut dt = new_dt(); gen_row_pointers(i) .map(|ptr| time(|| dt.contains(ptr)) + time(|| dt.insert(ptr))) .sum() }); bench_custom(&mut g, "mixed_random", |i| { let mut dt = new_dt(); let mut ptrs = gen_row_pointers(i).collect_vec(); let mut rng = ThreadRng::default(); ptrs.shuffle(&mut rng); ptrs.into_iter() .map(|ptr| time(|| dt.contains(ptr)) + time(|| dt.insert(ptr))) .sum() }); bench_custom(&mut g, "insert", |i| { let mut dt = new_dt(); gen_row_pointers(i).map(|ptr| time(|| dt.insert(ptr))).sum() }); bench_custom(&mut g, "contains_for_half", |i| { let mut dt = new_dt(); gen_row_pointers(i) .enumerate() .map(|(i, ptr)| { if i % 2 == 0 { black_box(dt.insert(ptr)); } time(|| dt.contains(ptr)) }) .sum() }); bench_custom(&mut g, "contains_for_full", |i| { let mut dt = new_dt(); gen_row_pointers(i) .map(|ptr| { black_box(dt.insert(ptr)); time(|| dt.contains(ptr)) }) .sum() }); bench_custom(&mut g, "remove", |i| { let mut dt = new_dt(); for ptr in gen_row_pointers(i) { black_box(dt.insert(ptr)); } gen_row_pointers(i).map(|ptr| time(|| dt.remove(ptr))).sum() }); bench_custom(&mut g, "iter", |i| { let mut dt = new_dt(); for ptr in gen_row_pointers(i) { black_box(dt.insert(ptr)); } time(|| dt.iter().count()) }); g.finish(); } trait DeleteTable { const NAME: &'static str; fn new(fixed_row_size: Size) -> Self; fn contains(&self, ptr: RowPointer) -> bool; fn insert(&mut self, ptr: RowPointer) -> bool; fn remove(&mut self, ptr: RowPointer) -> bool; fn iter(&self) -> impl Iterator; #[allow(unused)] fn len(&self) -> usize; } struct DTBTree(BTreeSet); impl DeleteTable for DTBTree { const NAME: &'static str = "DTBTree"; fn new(_: Size) -> Self { Self(<_>::default()) } fn contains(&self, ptr: RowPointer) -> bool { self.0.contains(&ptr) } fn insert(&mut self, ptr: RowPointer) -> bool { self.0.insert(ptr) } fn remove(&mut self, ptr: RowPointer) -> bool { self.0.remove(&ptr) } fn iter(&self) -> impl Iterator { self.0.iter().copied() } fn len(&self) -> usize { self.0.len() } } struct DTHashSet(HashSet); impl DeleteTable for DTHashSet { const NAME: &'static str = "DTHashSet"; fn new(_: Size) -> Self { Self(<_>::default()) } fn contains(&self, ptr: RowPointer) -> bool { self.0.contains(&ptr) } fn insert(&mut self, ptr: RowPointer) -> bool { self.0.insert(ptr) } fn remove(&mut self, ptr: RowPointer) -> bool { self.0.remove(&ptr) } fn iter(&self) -> impl Iterator { self.0.iter().copied() } fn len(&self) -> usize { self.0.len() } } struct DTHashSetFH(foldhash::HashSet); impl DeleteTable for DTHashSetFH { const NAME: &'static str = "DTHashSetFH"; fn new(_: Size) -> Self { Self(<_>::default()) } fn contains(&self, ptr: RowPointer) -> bool { self.0.contains(&ptr) } fn insert(&mut self, ptr: RowPointer) -> bool { self.0.insert(ptr) } fn remove(&mut self, ptr: RowPointer) -> bool { self.0.remove(&ptr) } fn iter(&self) -> impl Iterator { self.0.iter().copied() } fn len(&self) -> usize { self.0.len() } } type DTPageAndBitSet = delete_table::DeleteTable; impl DeleteTable for DTPageAndBitSet { const NAME: &'static str = "DTPageAndBitSet"; fn new(fixed_row_size: Size) -> Self { Self::new(fixed_row_size) } fn contains(&self, ptr: RowPointer) -> bool { self.contains(ptr) } fn insert(&mut self, ptr: RowPointer) -> bool { self.insert(ptr) } fn remove(&mut self, ptr: RowPointer) -> bool { self.remove(ptr) } fn iter(&self) -> impl Iterator { self.iter() } fn len(&self) -> usize { self.len() } } #[derive(Clone, Copy)] struct OffsetRange { start: PageOffset, end: PageOffset, } impl OffsetRange { fn point(offset: PageOffset) -> Self { Self { start: offset, end: offset, } } } type OffsetRanges = SmallVec<[OffsetRange; 4]>; struct DTPageAndOffsetRanges { deleted: Vec, len: usize, fixed_row_size: Size, } fn cmp_start_end(start: &T, end: &T, needle: &T) -> Ordering { match (start.cmp(needle), end.cmp(needle)) { // start = needle or start < offset <= end => we have a match. (Ordering::Less, Ordering::Equal | Ordering::Greater) | (Ordering::Equal, _) => Ordering::Equal, // start <= end < needle => no match. (Ordering::Less, Ordering::Less) => Ordering::Less, // start <= end > needle => no match. (Ordering::Greater, _) => Ordering::Greater, } } #[inline] fn find_range_to_insert_offset( ranges: &OffsetRanges, offset: PageOffset, fixed_row_size: Size, ) -> Result<(bool, bool, usize), usize> { let mut extend_end = true; let mut exists = false; ranges .binary_search_by(|&OffsetRange { start, end }| { extend_end = true; exists = false; match end.cmp(&offset) { // `end + row_size = offset` => we can just extend `end = offset`. Ordering::Less if end.0 + fixed_row_size.0 == offset.0 => Ordering::Equal, // Cannot extend this range, so let's not find it. Ordering::Less => Ordering::Less, // `offset` is already covered, so don't do anything, // but `end = offset` is a no-op. Ordering::Equal => { exists = true; Ordering::Equal } // `end` is greater, but we may be covered by `start` instead. Ordering::Greater => match start.cmp(&offset) { // `offset` is within the range, so don't do anything. Ordering::Less | Ordering::Equal => { exists = true; Ordering::Equal } // `start - row_size = offset` => we can just extend `start = offset`. Ordering::Greater if start.0 - fixed_row_size.0 == offset.0 => { extend_end = false; Ordering::Equal } // Range is entirely greater than offset. Ordering::Greater => Ordering::Greater, }, } }) .map(|idx| (extend_end, exists, idx)) } impl DeleteTable for DTPageAndOffsetRanges { const NAME: &'static str = "DTPageAndOffsetRanges"; fn new(fixed_row_size: Size) -> Self { Self { deleted: <_>::default(), len: 0, fixed_row_size, } } fn contains(&self, ptr: RowPointer) -> bool { let page_idx = ptr.page_index().idx(); let page_offset = ptr.page_offset(); match self.deleted.get(page_idx) { Some(ranges) => ranges .binary_search_by(|r| cmp_start_end(&r.start, &r.end, &page_offset)) .is_ok(), _ => false, } } fn insert(&mut self, ptr: RowPointer) -> bool { let fixed_row_size = self.fixed_row_size; let page_idx = ptr.page_index().idx(); let page_offset = ptr.page_offset(); let Some(ranges) = self.deleted.get_mut(page_idx) else { let pages = self.deleted.len(); let after = 1 + page_idx; self.deleted.reserve(after - pages); for _ in pages..after { self.deleted.push(SmallVec::new()); } self.deleted[page_idx].push(OffsetRange::point(page_offset)); self.len += 1; return true; }; let (extend_end, exists, range_idx) = match find_range_to_insert_offset(ranges, page_offset, fixed_row_size) { Err(range_idx) => { // Not found, so add a point range. ranges.insert(range_idx, OffsetRange::point(page_offset)); self.len += 1; return true; } Ok(x) => x, }; if extend_end { let next = range_idx + 1; let new_end = if let Some(r) = ranges .get(next) .copied() .filter(|r| r.start.0 - fixed_row_size.0 == page_offset.0) { ranges.remove(next); r.end } else { page_offset }; ranges[range_idx].end = new_end; } else { let prev = range_idx.saturating_sub(1); if let Some(r) = ranges .get(prev) .copied() .filter(|r| r.end.0 + fixed_row_size.0 == page_offset.0) { ranges[range_idx].start = r.start; ranges.remove(prev); } else { ranges[range_idx].start = page_offset; }; } let added = !exists; if added { self.len += 1; } added } fn remove(&mut self, ptr: RowPointer) -> bool { let fixed_row_size = self.fixed_row_size; let page_idx = ptr.page_index().idx(); let page_offset = ptr.page_offset(); let Some(ranges) = self.deleted.get_mut(page_idx) else { return false; }; let Ok(idx) = ranges.binary_search_by(|r| cmp_start_end(&r.start, &r.end, &page_offset)) else { return false; }; self.len -= 1; let range = &mut ranges[idx]; let is_start = range.start == page_offset; let is_end = range.end == page_offset; match (is_start, is_end) { // Remove the point range. (true, true) => drop(ranges.remove(idx)), // Narrow the start. (true, false) => range.start += fixed_row_size, // Narrow the end. (false, true) => range.end -= fixed_row_size, // Split the range. (false, false) => { // Derive the second range, to the right of the hole. let end = range.end; let start = PageOffset(page_offset.0 + fixed_row_size.0); let new = OffsetRange { start, end }; // Adjust the first range, to the left of the hole. range.end.0 = page_offset.0 - fixed_row_size.0; // Add the second range. ranges.insert(idx + 1, new); } } true } fn iter(&self) -> impl Iterator { (0..) .map(PageIndex) .zip(self.deleted.iter()) .flat_map(move |(pi, ranges)| { ranges .iter() .flat_map(|range| (range.start.0..=range.end.0).step_by(self.fixed_row_size.0 as usize)) .map(PageOffset) .map(move |po| RowPointer::new(false, pi, po, SquashedOffset::COMMITTED_STATE)) }) } fn len(&self) -> usize { self.len } } criterion_group!( delete_table, bench_delete_table::, bench_delete_table::, bench_delete_table::, bench_delete_table::, bench_delete_table::, // best so far. ); criterion_main!(delete_table); ================================================ FILE: crates/bench/benches/generic.rs ================================================ use criterion::{ criterion_group, criterion_main, measurement::{Measurement, WallTime}, Bencher, BenchmarkGroup, Criterion, }; use lazy_static::lazy_static; use spacetimedb_bench::{ database::BenchDatabase, schemas::{create_sequential, u32_u64_str, u32_u64_u64, BenchTable, IndexStrategy, RandomTable}, spacetime_module, spacetime_raw, sqlite, ResultBench, }; use spacetimedb_lib::sats::AlgebraicType; use spacetimedb_primitives::ColId; use spacetimedb_testing::modules::{Csharp, Rust}; #[cfg(target_env = "msvc")] #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; #[cfg(not(target_env = "msvc"))] use tikv_jemallocator::Jemalloc; #[cfg(not(target_env = "msvc"))] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; lazy_static! { static ref RUN_ONE_MILLION: bool = std::env::var("RUN_ONE_MILLION").is_ok(); } fn criterion_benchmark(c: &mut Criterion) { bench_suite::(c, true).unwrap(); bench_suite::(c, true).unwrap(); bench_suite::>(c, true).unwrap(); bench_suite::>(c, true).unwrap(); bench_suite::(c, false).unwrap(); bench_suite::(c, false).unwrap(); bench_suite::>(c, false).unwrap(); bench_suite::>(c, false).unwrap(); } #[inline(never)] fn bench_suite(c: &mut Criterion, in_memory: bool) -> ResultBench<()> { let mut db = DB::build(in_memory)?; let param_db_name = DB::name(); let param_in_memory = if in_memory { "mem" } else { "disk" }; let db_params = format!("{param_db_name}/{param_in_memory}"); let mut g = c.benchmark_group(&db_params); empty(&mut g, &mut db)?; table_suite::(&mut g, &mut db)?; table_suite::(&mut g, &mut db)?; Ok(()) } type Group<'a> = BenchmarkGroup<'a, WallTime>; #[inline(never)] fn table_suite(g: &mut Group, db: &mut DB) -> ResultBench<()> { // This setup is a compromise between trying to present related benchmarks together, // and not having to deal with nasty reentrant generic dispatching. type TableData = (IndexStrategy, TableId, String); let mut prep_table = |index_strategy: IndexStrategy| -> ResultBench> { let table_name = T::name(); let style_name = index_strategy.name(); let table_params = format!("{table_name}/{style_name}"); let table_id = db.create_table::(index_strategy)?; Ok((index_strategy, table_id, table_params)) }; let tables: [TableData; 2] = [ prep_table(IndexStrategy::Unique0)?, //prep_table(IndexStrategy::NoIndex)?, prep_table(IndexStrategy::BTreeEachColumn)?, ]; for (_, table_id, table_params) in &tables { insert_bulk::(g, table_params, db, table_id, 2048, 256)?; if *RUN_ONE_MILLION { insert_bulk::(g, table_params, db, table_id, 0, 1_000_000)?; } } for (index_strategy, table_id, table_params) in &tables { if *index_strategy == IndexStrategy::Unique0 { // Iterate is unaffected by index strategy, so only run it here iterate::(g, table_params, db, table_id, 256)?; // Update can only be performed with a unique key update_bulk::(g, table_params, db, table_id, 2048, 256)?; if *RUN_ONE_MILLION { update_bulk::(g, table_params, db, table_id, 1_000_000, 1_000_000)?; } } else { // perform "filter" benchmarks filter::(g, db, table_id, index_strategy, 2, 2048, 8)?; } } Ok(()) } /// Custom criterion timing loop. Allows access to a database, which is reset every benchmark iteration. /// /// The prepare closure should restore the database to known state /// and prepare any inputs consumed by the benchmark. The time this takes will /// not be measured. /// /// You should clear all modified tables after calling this, just to be safe. #[inline(never)] fn bench_harness< DB: BenchDatabase, INPUT, PREPARE: FnMut(&mut DB) -> ResultBench, ROUTINE: FnMut(&mut DB, INPUT) -> ResultBench<()>, >( b: &mut Bencher, db: &mut DB, mut prepare: PREPARE, mut routine: ROUTINE, ) { b.iter_custom(|n| { let timer = WallTime; let mut elapsed = timer.zero(); for _ in 0..n { let input = prepare(db).unwrap(); // only nanoseconds of overhead let start = timer.start(); routine(db, input).unwrap(); let just_elapsed = timer.end(start); elapsed = timer.add(&elapsed, &just_elapsed); } elapsed }); } #[inline(never)] fn empty(g: &mut Group, db: &mut DB) -> ResultBench<()> { let id = "empty".to_string(); g.bench_function(&id, |b| { bench_harness( b, db, |_| Ok(()), |db, _| { // not much to do in this one db.empty_transaction() }, ) }); Ok(()) } #[inline(never)] fn insert_bulk( g: &mut Group, table_params: &str, db: &mut DB, table_id: &DB::TableId, load: u32, count: u32, ) -> ResultBench<()> { let id = format!("insert_bulk/{table_params}/load={load}/count={count}"); let data = create_sequential::(0xdeadbeef, load + count, 1000); // Each iteration performs one transaction, though it inserts many rows. g.throughput(criterion::Throughput::Elements(1)); // FIXME: only for 1_000_000 inserts g.sample_size(10); g.bench_function(&id, |b| { bench_harness( b, db, |db| { let mut data = data.clone(); db.clear_table(table_id)?; let to_insert = data.split_off(load as usize); if !data.is_empty() { db.insert_bulk(table_id, data)?; } Ok(to_insert) }, |db, to_insert| { db.insert_bulk(table_id, to_insert)?; Ok(()) }, ) }); db.clear_table(table_id)?; Ok(()) } #[inline(never)] fn update_bulk( g: &mut Group, table_params: &str, db: &mut DB, table_id: &DB::TableId, load: u32, count: u32, ) -> ResultBench<()> { let id = format!("update_bulk/{table_params}/load={load}/count={count}"); let data = create_sequential::(0xdeadbeef, load, 1000); // Each iteration performs one transaction, though it inserts many rows. g.throughput(criterion::Throughput::Elements(1)); // running a big guy g.sample_size(10); g.bench_function(&id, |b| { bench_harness( b, db, |db| { let data = data.clone(); db.clear_table(table_id)?; db.insert_bulk(table_id, data)?; Ok(()) }, |db, _| { db.update_bulk::(table_id, count)?; Ok(()) }, ) }); db.clear_table(table_id)?; Ok(()) } #[inline(never)] fn iterate( g: &mut Group, table_params: &str, db: &mut DB, table_id: &DB::TableId, count: u32, ) -> ResultBench<()> { let id = format!("iterate/{table_params}/count={count}"); let data = create_sequential::(0xdeadbeef, count, 1000); db.insert_bulk(table_id, data)?; // Each iteration performs a single transaction, // though it iterates across many rows. g.throughput(criterion::Throughput::Elements(1)); g.bench_function(&id, |b| { bench_harness( b, db, |_| Ok(()), |db, _| { db.iterate(table_id)?; Ok(()) }, ) }); db.clear_table(table_id)?; Ok(()) } #[inline(never)] fn filter( g: &mut Group, db: &mut DB, table_id: &DB::TableId, index_strategy: &IndexStrategy, col_id: impl Into, load: u32, buckets: u32, ) -> ResultBench<()> { let col_id = col_id.into(); let filter_column_type = match T::product_type().elements[col_id.idx()].algebraic_type { AlgebraicType::String => "string", AlgebraicType::U32 => "u32", AlgebraicType::U64 => "u64", _ => unimplemented!(), }; let mean_result_count = load / buckets; let indexed = match index_strategy { IndexStrategy::BTreeEachColumn => "index", IndexStrategy::NoIndex => "no_index", _ => unimplemented!(), }; let id = format!("filter/{filter_column_type}/{indexed}/load={load}/count={mean_result_count}"); let data = create_sequential::(0xdeadbeef, load, buckets as u64); db.insert_bulk(table_id, data.clone())?; // Each iteration performs a single transaction. g.throughput(criterion::Throughput::Elements(1)); // We loop through all buckets found in the sample data. // This mildly increases variance on the benchmark, but makes "mean_result_count" more accurate. // Note that all databases have EXACTLY the same sample data. let mut i = 0; g.bench_function(&id, |b| { bench_harness( b, db, |_| { // pick something to look for let value = data[i].clone().into_product_value().elements[col_id.idx()].clone(); i = (i + 1) % load as usize; Ok(value) }, |db, value| { db.filter::(table_id, col_id, value)?; Ok(()) }, ) }); db.clear_table(table_id)?; Ok(()) } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: crates/bench/benches/index.rs ================================================ use core::{any::type_name, hash::BuildHasherDefault, hint::black_box, iter::repeat_with, mem, time::Duration}; use criterion::{ criterion_group, criterion_main, measurement::{Measurement as _, WallTime}, BenchmarkGroup, Criterion, }; use foldhash::{HashSet, HashSetExt}; use hashbrown::{hash_map::Entry, HashMap}; use itertools::Itertools as _; use rand::{ distr::{Distribution, StandardUniform}, Rng, }; use spacetimedb_lib::AlgebraicValue; use spacetimedb_sats::{layout::Size, product, u256}; use spacetimedb_table::indexes::{PageIndex, PageOffset, RowPointer, SquashedOffset}; use spacetimedb_table::table_index::uniquemap::UniqueMap; use spacetimedb_table::table_index::Index as _; use spacetimedb_table::table_index::{ unique_direct_index::{ToFromUsize, UniqueDirectIndex}, KeySize, }; use std::hash::Hash; fn time(body: impl FnOnce() -> R) -> Duration { let start = WallTime.start(); let ret = body(); let end = WallTime.end(start); black_box(ret); end } const FIXED_ROW_SIZE: Size = Size(4 * 4); fn gen_row_pointers() -> impl Iterator { let mut page_index = PageIndex(0); let mut page_offset = PageOffset(0); repeat_with(move || { if page_offset.0 as usize + FIXED_ROW_SIZE.0 as usize >= PageOffset::PAGE_END.0 as usize { // Consumed the page, let's use a new page. page_index.0 += 1; page_offset = PageOffset(0); } else { page_offset += FIXED_ROW_SIZE; } black_box(RowPointer::new( false, page_index, page_offset, SquashedOffset::COMMITTED_STATE, )) }) } fn bench_custom(g: &mut BenchmarkGroup<'_, WallTime>, name: &str, run: impl Fn(u64) -> Duration) { g.bench_function(name, |b| b.iter_custom(&run)); } /// Returns an iterator with the keys `0..n`. fn monotonic_keys(n: u64) -> impl Clone + Iterator { (0 as MonoKey..).take(n as usize) } // Returns a set with `n` distinct random keys. fn random_keys(n: u64) -> HashSet where StandardUniform: Distribution, { let desired_len = n as usize; let mut set = HashSet::with_capacity(desired_len); let mut iter = rand::random_iter(); while set.len() < desired_len { set.insert(iter.next().unwrap()); } set } /// Times inserting `keys` to the index. fn time_insertions(keys: impl Iterator) -> Duration { let mut index = I::new(); keys.zip(gen_row_pointers()) .map(black_box) .map(|(key, ptr)| time(|| index.insert(key, ptr))) .sum() } /// Times inserting monotonically increasing keys to the index. /// /// The benchmark intentionally times N keys rather than the Nth key. fn bench_insert_monotonic>(g: &mut BenchmarkGroup<'_, WallTime>) { bench_custom(g, "insert_monotonic", |n| time_insertions::(monotonic_keys(n))); } /// Times inserting random keys to the index. /// /// The benchmark intentionally times N keys rather than the Nth key. fn bench_insert_random(g: &mut BenchmarkGroup<'_, WallTime>) where StandardUniform: Distribution, { bench_custom(g, "insert_random", |n| time_insertions::(random_keys(n).into_iter())); } /// Times seeking `keys` in the index. fn time_seeks(keys: impl Clone + Iterator) -> Duration { // Prepare index with the keys. let mut index = I::new(); for (key, ptr) in keys.clone().zip(gen_row_pointers()) { index.insert(key, ptr).unwrap(); } // Time seeking every K in keys. keys.map(black_box) .map(|i| time(|| black_box(index.seek(i)).next())) .sum() } /// Times seeking all keys index with monotonically increasing keys. fn bench_seek_monotonic>(g: &mut BenchmarkGroup<'_, WallTime>) { bench_custom(g, "seek_monotonic", |n| time_seeks::(monotonic_keys(n))); } /// Times seeking all keys in an index with random keys. fn bench_seek_random(g: &mut BenchmarkGroup<'_, WallTime>) where StandardUniform: Distribution, { bench_custom(g, "seek_random", |n| { let keys = random_keys(n); let keys2 = keys.iter().cloned().sorted(); // Prepare index with the keys. let mut index = I::new(); for (key, ptr) in keys2.clone().zip(gen_row_pointers()) { index.insert(key, ptr).unwrap(); } // Time seeking every K in keys. keys.into_iter() .map(black_box) .map(|i| time(|| black_box(index.seek(i)).next())) .sum() }); } /// Times deleting `keys` in the index. fn time_deletes(keys: impl Clone + IntoIterator) -> Duration { // Prepare index with the keys. let mut index = I::new(); for (key, ptr) in keys.clone().into_iter().zip(gen_row_pointers()) { index.insert(key, ptr).unwrap(); } // Time deleting every K in keys. keys.into_iter().map(black_box).map(|i| time(|| index.delete(i))).sum() } /// Times deleting for one key in an index with monotonically increasing keys. fn bench_delete_monotonic>(g: &mut BenchmarkGroup<'_, WallTime>) { bench_custom(g, "delete_monotonic", |n| time_deletes::(monotonic_keys(n))); } /// Times seeking for one key in an index with monotonically increasing keys. fn bench_delete_random(g: &mut BenchmarkGroup<'_, WallTime>) where StandardUniform: Distribution, { bench_custom(g, "delete_random", |n| time_deletes::(random_keys(n))); } fn bench_index_mono>(c: &mut Criterion) { let mut g = c.benchmark_group(type_name::()); bench_insert_monotonic::(&mut g); bench_insert_random::(&mut g); bench_seek_monotonic::(&mut g); bench_seek_random::(&mut g); bench_delete_monotonic::(&mut g); bench_delete_random::(&mut g); } fn bench_index_random(c: &mut Criterion) where StandardUniform: Distribution, { let mut g = c.benchmark_group(type_name::()); bench_insert_random::(&mut g); bench_seek_random::(&mut g); bench_delete_random::(&mut g); } type MonoKey = u32; trait Index: Clone { type K: Eq + Hash + Ord + Clone; fn new() -> Self; fn insert(&mut self, key: Self::K, val: RowPointer) -> Result<(), RowPointer>; fn seek(&self, key: Self::K) -> impl Iterator; fn delete(&mut self, key: Self::K) -> bool; } #[derive(Clone)] struct IBTree>(UniqueMap); impl + Clone + Eq + Hash + Ord> Index for IBTree { type K = K; fn new() -> Self { Self(<_>::default()) } fn insert(&mut self, key: Self::K, val: RowPointer) -> Result<(), RowPointer> { self.0.insert(key, val) } fn seek(&self, key: Self::K) -> impl Iterator { self.0.seek_point(&key) } fn delete(&mut self, key: Self::K) -> bool { self.0.delete(&key, RowPointer(0)) } } #[derive(Clone)] struct IAHash(HashMap>); impl Index for IAHash { type K = K; fn new() -> Self { Self(<_>::default()) } fn insert(&mut self, key: Self::K, val: RowPointer) -> Result<(), RowPointer> { match self.0.entry(key) { Entry::Vacant(e) => { e.insert(val); Ok(()) } Entry::Occupied(e) => Err(*e.into_mut()), } } fn seek(&self, key: Self::K) -> impl Iterator { self.0.get(&key).into_iter().copied() } fn delete(&mut self, key: Self::K) -> bool { self.0.remove(&key).is_some() } } #[derive(Clone)] struct IFoldHash(HashMap); impl Index for IFoldHash { type K = K; fn new() -> Self { Self(<_>::default()) } fn insert(&mut self, key: Self::K, val: RowPointer) -> Result<(), RowPointer> { match self.0.entry(key) { Entry::Vacant(e) => { e.insert(val); Ok(()) } Entry::Occupied(e) => Err(*e.into_mut()), } } fn seek(&self, key: Self::K) -> impl Iterator { self.0.get(&key).into_iter().copied() } fn delete(&mut self, key: Self::K) -> bool { self.0.remove(&key).is_some() } } #[derive(Clone)] struct IDirectIndex(UniqueDirectIndex); impl Index for IDirectIndex { type K = K; fn new() -> Self { Self(<_>::default()) } fn insert(&mut self, key: Self::K, val: RowPointer) -> Result<(), RowPointer> { self.0.insert(key, val) } fn seek(&self, key: Self::K) -> impl Iterator { self.0.seek_point(&key) } fn delete(&mut self, key: Self::K) -> bool { self.0.delete(&key, RowPointer(0)) } } /* Complex keys */ #[derive(Clone, Copy)] struct U256(u256); impl Distribution for StandardUniform { fn sample(&self, rng: &mut R) -> U256 { let (hi, lo) = self.sample(rng); U256(u256::from_words(hi, lo)) } } impl From for AlgebraicValue { fn from(value: U256) -> AlgebraicValue { AlgebraicValue::U256(Box::new(value.0)) } } impl U256 { fn to_le_bytes(self) -> [u8; 32] { self.0.to_le_bytes() } } macro_rules! av_enc_type { ($name_av:ident, $name_enc:ident, $sample_ty:ty, ($($sample_pat:ident),* $(,)?)) => { #[allow(non_camel_case_types)] #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] struct $name_av(AlgebraicValue); #[allow(non_camel_case_types)] #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] struct $name_enc([u8; mem::size_of::<$sample_ty>()]); impl KeySize for $name_av { type MemoStorage = (); } impl KeySize for $name_enc { type MemoStorage = (); } impl Distribution<$name_av> for StandardUniform { fn sample(&self, rng: &mut R) -> $name_av { let ($($sample_pat,)*): $sample_ty = self.sample(rng); $name_av(product![$($sample_pat),*].into()) } } impl Distribution<$name_enc> for StandardUniform { #[allow(unused_assignments)] fn sample(&self, rng: &mut R) -> $name_enc { let ($($sample_pat,)*): $sample_ty = self.sample(rng); let mut enc = [0; _]; let mut start = 0; $( let size = mem::size_of_val(&$sample_pat); enc[start..start + size].copy_from_slice(&$sample_pat.to_le_bytes()); start += size; )* $name_enc(enc) } } }; } av_enc_type!( U16xU256xU32xU256_AV, U16xU256xU32xU256_Enc, (u16, U256, u32, U256), (a, b, c, d) ); av_enc_type!(U16xU256xU32_AV, U16xU256xU32_Enc, (u16, U256, u32), (a, b, c)); av_enc_type!(U256xU32xBool_AV, U256xU32xBool_Enc, (U256, u32, u8), (a, b, c)); av_enc_type!(U256xU32_AV, U256xU32_Enc, (U256, u32), (a, b)); // This is the index x_z_chunk_index from // https://github.com/clockworklabs/BitCraft/blob/f8177345df1e4ec54b91acee94146e75a0525548/BitCraftServer/packages/game/src/messages/components.rs#L224C18-L224C33 av_enc_type!(I32xI32xU64_AV, I32xI32xU64_Enc, (i32, i32, u64), (a, b, c)); criterion_group!( delete_table, // Primitive types, both random and monotonic keys: bench_index_mono::>, bench_index_mono::>, bench_index_mono::>, bench_index_mono::>, // Complex keys, hash index: bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, // Complex keys, btree index: bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, bench_index_random::>, ); criterion_main!(delete_table); ================================================ FILE: crates/bench/benches/special.rs ================================================ use criterion::async_executor::AsyncExecutor; use criterion::{criterion_group, criterion_main, Criterion, SamplingMode}; use spacetimedb_bench::{ database::BenchDatabase, schemas::{create_sequential, u32_u64_str, u32_u64_u64, u64_u64_u32, BenchTable, RandomTable}, spacetime_module::SpacetimeModule, }; use spacetimedb_lib::sats::{self, bsatn}; use spacetimedb_lib::{bsatn::ToBsatn as _, ProductValue}; use spacetimedb_schema::schema::TableSchema; use spacetimedb_schema::table_name::TableName; use spacetimedb_table::page_pool::PagePool; use spacetimedb_testing::modules::{Csharp, ModuleLanguage, Rust}; use std::sync::Arc; use std::sync::OnceLock; #[cfg(target_env = "msvc")] #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; #[cfg(not(target_env = "msvc"))] use tikv_jemallocator::Jemalloc; #[cfg(not(target_env = "msvc"))] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; fn criterion_benchmark(c: &mut Criterion) { serialize_benchmarks::(c); serialize_benchmarks::(c); serialize_benchmarks::(c); custom_benchmarks::(c); custom_benchmarks::(c); } fn custom_benchmarks(c: &mut Criterion) { let db = SpacetimeModule::::build(true).unwrap(); custom_module_benchmarks(&db, c); custom_db_benchmarks(&db, c); } fn custom_module_benchmarks(m: &SpacetimeModule, c: &mut Criterion) { let mut group = c.benchmark_group(format!("special/{}", SpacetimeModule::::name())); let args = sats::product!["0".repeat(65536).into_boxed_str()]; group.bench_function("large_arguments/64KiB", |b| { b.to_async(m) .iter(|| async { m.module.call_reducer_binary("fn_with_1_args", &args).await.unwrap() }) }); for n in [1u32, 100, 1000] { let args = sats::product![n]; group.bench_function(format!("print_bulk/lines={n}"), |b| { b.to_async(m) .iter(|| async { m.module.call_reducer_binary("print_many_things", &args).await.unwrap() }) }); } } fn custom_db_benchmarks(m: &SpacetimeModule, c: &mut Criterion) { let mut group = c.benchmark_group(format!("special/db_game/{}", L::NAME)); // This bench take long, so adjust for it group.sample_size(10); group.sampling_mode(SamplingMode::Flat); let init_db: OnceLock<()> = OnceLock::new(); for n in [10, 100] { let args = sats::product![n]; group.bench_function(format!("circles/load={n}"), |b| { // Initialize outside the benchmark so the db is seed once, to avoid to enlarge the db init_db.get_or_init(|| { m.block_on(async { m.module .call_reducer_binary("init_game_circles", &sats::product![100]) .await .unwrap() }); }); b.to_async(m) .iter(|| async { m.module.call_reducer_binary("run_game_circles", &args).await.unwrap() }) }); } let init_db: OnceLock<()> = OnceLock::new(); for n in [10, 100] { let args = sats::product![n]; group.bench_function(format!("ia_loop/load={n}"), |b| { // Initialize outside the benchmark so the db is seed once, to avoid `unique` constraints violations init_db.get_or_init(|| { m.block_on(async { m.module .call_reducer_binary("init_game_ia_loop", &sats::product![500]) .await .unwrap(); }) }); b.to_async(m) .iter(|| async { m.module.call_reducer_binary("run_game_ia_loop", &args).await.unwrap() }) }); } } fn serialize_benchmarks< T: BenchTable + RandomTable + for<'a> spacetimedb_lib::de::Deserialize<'a> + for<'a> serde::de::Deserialize<'a>, >( c: &mut Criterion, ) { let name = T::name(); let count = 100; let mut group = c.benchmark_group(format!("special/serde/serialize/{name}")); group.throughput(criterion::Throughput::Elements(count)); let data = create_sequential::(0xdeadbeef, count as u32, 100); group.bench_function(format!("product_value/count={count}"), |b| { b.iter_batched( || data.clone(), |data| data.into_iter().map(|row| row.into_product_value()).collect::>(), criterion::BatchSize::PerIteration, ); }); // this measures serialization from a ProductValue, not directly (as in, from generated code in the Rust SDK.) let data_pv = &data .into_iter() .map(|row| spacetimedb_lib::AlgebraicValue::Product(row.into_product_value())) .collect::(); group.bench_function(format!("bsatn/count={count}"), |b| { b.iter(|| sats::bsatn::to_vec(data_pv).unwrap()); }); group.bench_function(format!("json/count={count}"), |b| { b.iter(|| serde_json::to_string(data_pv).unwrap()); }); let mut table_schema = TableSchema::from_product_type(T::product_type()); table_schema.table_name = TableName::for_test(name); let mut table = spacetimedb_table::table::Table::new( Arc::new(table_schema), spacetimedb_table::indexes::SquashedOffset::COMMITTED_STATE, ); let pool = PagePool::new_for_test(); let mut blob_store = spacetimedb_table::blob_store::HashMapBlobStore::default(); let ptrs = data_pv .elements .iter() .map(|row| { table .insert(&pool, &mut blob_store, row.as_product().unwrap()) .unwrap() .1 .pointer() }) .collect::>(); let refs = ptrs .into_iter() .map(|ptr| table.get_row_ref(&blob_store, ptr).unwrap()) .collect::>(); group.bench_function(format!("bflatn_to_bsatn_slow_path/count={count}"), |b| { b.iter(|| { let mut buf = Vec::new(); for row_ref in &refs { bsatn::to_writer(&mut buf, row_ref).unwrap(); } buf }) }); group.bench_function(format!("bflatn_to_bsatn_fast_path/count={count}"), |b| { b.iter(|| { let mut buf = Vec::new(); for row_ref in &refs { row_ref.to_bsatn_extend(&mut buf).unwrap(); } buf }); }); group.finish(); let mut group = c.benchmark_group(format!("special/serde/deserialize/{name}")); group.throughput(criterion::Throughput::Elements(count)); let data_bin = sats::bsatn::to_vec(&data_pv).unwrap(); let data_json = serde_json::to_string(&data_pv).unwrap(); group.bench_function(format!("bsatn/count={count}"), |b| { b.iter(|| bsatn::from_slice::>(&data_bin).unwrap()); }); group.bench_function(format!("json/count={count}"), |b| { b.iter(|| serde_json::from_str::>(&data_json).unwrap()); }); // TODO: deserialize benches (needs a typespace) } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: crates/bench/benches/subscription.rs ================================================ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use spacetimedb::client::consume_each_list::ConsumeEachBuffer; use spacetimedb::db::relational_db::RelationalDB; use spacetimedb::error::DBError; use spacetimedb::identity::AuthCtx; use spacetimedb::sql::ast::SchemaViewer; use spacetimedb::subscription::row_list_builder_pool::BsatnRowListBuilderPool; use spacetimedb::subscription::tx::DeltaTx; use spacetimedb::subscription::{collect_table_update, TableUpdateType}; use spacetimedb_bench::database::BenchDatabase as _; use spacetimedb_bench::spacetime_raw::SpacetimeRaw; use spacetimedb_client_api_messages::websocket::v1::BsatnFormat; use spacetimedb_datastore::execution_context::Workload; use spacetimedb_execution::pipelined::PipelinedProject; use spacetimedb_primitives::{col_list, TableId}; use spacetimedb_query::compile_subscription; use spacetimedb_sats::{bsatn, product, AlgebraicType, AlgebraicValue}; #[cfg(not(target_env = "msvc"))] use tikv_jemallocator::Jemalloc; #[cfg(not(target_env = "msvc"))] #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; fn create_table_location(db: &RelationalDB) -> Result { let schema = &[ ("entity_id", AlgebraicType::U64), ("chunk_index", AlgebraicType::U64), ("x", AlgebraicType::I32), ("z", AlgebraicType::I32), ("dimension", AlgebraicType::U32), ]; let indexes = &[0.into(), 1.into()]; // Is necessary to test for both single & multi-column indexes... db.create_table_for_test_mix_indexes("location", schema, indexes, col_list![2, 3, 4]) } fn create_table_footprint(db: &RelationalDB) -> Result { let footprint = AlgebraicType::sum(["A", "B", "C", "D"].map(|n| (n, AlgebraicType::unit()))); let schema = &[ ("entity_id", AlgebraicType::U64), ("owner_entity_id", AlgebraicType::U64), ("type", footprint), ]; let indexes = &[0.into(), 1.into()]; db.create_table_for_test("footprint", schema, indexes) } fn eval(c: &mut Criterion) { let raw = SpacetimeRaw::build(false).unwrap(); let lhs = create_table_footprint(&raw.db).unwrap(); let rhs = create_table_location(&raw.db).unwrap(); //TODO: Change this to `Workload::ForTest` once `#[cfg(bench)]` is stabilized. let _ = raw .db .with_auto_commit(Workload::Internal, |tx| -> Result<(), DBError> { // 1M rows let mut scratch = Vec::new(); for entity_id in 0u64..1_000_000 { let owner = entity_id % 1_000; let footprint = AlgebraicValue::sum(entity_id as u8 % 4, AlgebraicValue::unit()); let row = product!(entity_id, owner, footprint); scratch.clear(); bsatn::to_writer(&mut scratch, &row).unwrap(); let _ = raw.db.insert(tx, lhs, &scratch)?; } Ok(()) }); let _ = raw .db .with_auto_commit(Workload::Internal, |tx| -> Result<(), DBError> { // 1000 chunks, 1200 rows per chunk = 1.2M rows let mut scratch = Vec::new(); for chunk_index in 0u64..1_000 { for i in 0u64..1200 { let entity_id = chunk_index * 1200 + i; let x = 0i32; let z = entity_id as i32; let dimension = 0u32; let row = product!(entity_id, chunk_index, x, z, dimension); scratch.clear(); bsatn::to_writer(&mut scratch, &row).unwrap(); let _ = raw.db.insert(tx, rhs, &scratch)?; } } Ok(()) }); let entity_id = 1_200_000u64; let chunk_index = 5u64; let x = 0i32; let z = 0i32; let dimension = 0u32; let footprint = AlgebraicValue::sum(1, AlgebraicValue::unit()); let owner = 6u64; let _new_lhs_row = product!(entity_id, owner, footprint); let _new_rhs_row = product!(entity_id, chunk_index, x, z, dimension); let bsatn_rlb_pool = black_box(BsatnRowListBuilderPool::new()); // A benchmark runner for the subscription engine. let bench_query = |c: &mut Criterion, name, sql| { c.bench_function(name, |b| { let tx = raw.db.begin_tx(Workload::Subscribe); let auth = AuthCtx::for_testing(); let schema_viewer = &SchemaViewer::new(&tx, &auth); let (plans, table_id, table_name, _) = compile_subscription(sql, schema_viewer, &auth).unwrap(); let plans = plans .into_iter() .map(|plan| plan.optimize(&auth).unwrap()) .map(PipelinedProject::from) .collect::>(); let tx = DeltaTx::from(&tx); b.iter(|| { let updates = black_box(collect_table_update::( &plans, table_id, table_name.clone(), &tx, TableUpdateType::Subscribe, &bsatn_rlb_pool, )); if let Ok((updates, _)) = updates { updates.consume_each_list(&mut |buffer| bsatn_rlb_pool.try_put(buffer)); } }) }); }; // Join 1M rows on the left with 12K rows on the right. // Note, this should use an index join so as not to read the entire footprint table. let semijoin = format!( r#" select f.* from footprint f join location l on f.entity_id = l.entity_id where l.chunk_index = {chunk_index} "# ); let index_scan_multi = "select * from location WHERE x = 0 AND z = 10000 AND dimension = 0"; bench_query(c, "footprint-scan", "select * from footprint"); bench_query(c, "footprint-semijoin", &semijoin); bench_query(c, "index-scan-multi", index_scan_multi); } criterion_group!(benches, eval); criterion_main!(benches); ================================================ FILE: crates/bench/callgrind-docker.sh ================================================ #!/bin/bash # script to enter iai dockerfile locally set -exo pipefail SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" cd "$SCRIPT_DIR" docker build . --tag rust-iai-callgrind:latest docker run --privileged -v "$(realpath $PWD/../..):/projects/SpacetimeDB" -w /projects/SpacetimeDB/crates/bench rust-iai-callgrind:latest cargo bench --bench callgrind ================================================ FILE: crates/bench/clippy.toml ================================================ # we use println in summarize.rs, don't complain about it disallowed-macros = [] ================================================ FILE: crates/bench/flamegraph.sh ================================================ #!/bin/bash set -euo pipefail if [ "$#" -lt "2" ] ; then echo "Usage: $0 " echo "E.g.: $0 generic stdb_raw/mem/insert_bulk/location/multi_index/load=0/count=100 result.svg" exit 1 fi cd "$(dirname "$0")" echo echo "Warning(jgilles): this script has not been tested since its last modification, sorry if it is broken." echo echo cargo bench --no-run echo cargo bench --no-run echo echo cargo flamegraph --bench "${1}" --deterministic --notes "${1};${2}" -o "${3}" -- "${2}" --profile-time 10 echo cargo flamegraph --bench "${1}" --deterministic --notes "${1};${2}" -o "${3}" -- "${2}" --profile-time 10 ================================================ FILE: crates/bench/hyper_cmp.py ================================================ # Mini-tool for comparing benchmark between PR or locally import os import json import argparse import subprocess import shutil def statDecoder(statDict): return namedtuple('Stat', statDict.keys())(*statDict.values()) def clean(stat): new = {} for (k, v) in stat.items(): if k in ["stddev", "command", "exit_codes"]: continue if k == "times": v = v[0] if isinstance(v, float): v = round(v, 2) new[k] = v return new def larger(row: list): return [len(str(x)) for x in row] def bar_chart(data: list): max_value = max(count for _, count in data.items()) increment = max_value / 25 longest_label_length = max(len(label) for label, _ in data.items()) for label, count in data.items(): # The ASCII block elements come in chunks of 8, so we work out how # many fractions of 8 we need. # https://en.wikipedia.org/wiki/Block_Elements bar_chunks, remainder = divmod(int(count * 8 / increment), 8) # First draw the full width chunks bar = '█' * bar_chunks # Then add the fractional part. The Unicode code points for # block elements are (8/8), (7/8), (6/8), ... , so we need to # work backwards. if remainder > 0: bar += chr(ord('█') + (8 - remainder)) # If the bar is empty, add a left one-eighth block bar = bar or '▏' print(f'{label.rjust(longest_label_length)} ▏ {count:#4f} {bar}') class Report: def __init__(self, title: str, header: list, bar: dict, rows: dict): self.title = title size = larger(header) for row in rows: size = size + larger(row) self.header = header self.bar = bar self.rows = rows self.larger = max(size) + 2 class Stat: def __init__(self, results): self.spacetime = clean(results[0]) self.sqlite = clean(results[1]) def load_file(named: str): data = open(named).read() return Stat(json.loads(data)['results']) def print_cell(cell: str, size: int, is_last: bool): spaces = " " * (size - len(cell)) if is_last: return "| %s%s |" % (cell, spaces) else: return "| %s%s " % (cell, spaces) def print_row(row: list, size: int): line = "" for (pos, x) in enumerate(row): line += print_cell(str(x), size, pos + 1 == len(row)) print(line) def print_mkdown(report: Report): print("###", report.title) print("\n```bash") bar_chart(report.bar) print("```\n") print("*Smaller is better.*") print_row(report.header, report.larger) print_row(["-" * report.larger for x in report.header], report.larger) for row in report.rows: print_row(row, report.larger) def pick_winner(a: dict, b: dict, label_a: str, label_b: str): if a["mean"] > b["mean"]: winner = label_b delta = a["times"] else: if a["mean"] == b["mean"]: winner = "TIE" delta = a["times"] else: winner = label_a delta = b["times"] return winner, delta # Check Sqlite VS Spacetime def cmp_bench(stat: Stat): winner, delta = pick_winner(stat.spacetime, stat.sqlite, "Spacetime", "Sqlite") header = ["Stat", "Sqlite", "Spacetime", "Delta"] rows = [] for (k, v) in stat.sqlite.items(): rows.append([k, v, stat.spacetime[k], round(stat.spacetime[k] - v, 2)]) bar = dict(SpacetimeDB=stat.spacetime["mean"], Sqlite=stat.sqlite["mean"]) return Report("Comparing Sqlite VS Spacetime Winner: **%s**" % winner, header, bar, rows) # Check the progress of Spacetime between branches / PR def improvement_bench(old: Stat, new: Stat): winner, delta = pick_winner(old.spacetime, new.spacetime, "Old", "New") header = ["Stat", "OLD", "NEW", "Delta"] rows = [] for (k, v) in old.spacetime.items(): rows.append([k, v, new.spacetime[k], round(v - new.spacetime[k], 2)]) bar = dict(Old=old.spacetime["mean"], New=new.spacetime["mean"]) return Report("Improvement of Spacetime. Winner: **%s**" % winner, header, bar, rows) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("bench", choices=['versus', 'pr'], help="Select bench") args = vars(parser.parse_args()) # args = {"bench": "pr"} cmd = "./hyperfine.sh insert" if args["bench"] == "pr": subprocess.check_call('./pr_copy.sh "%s"' % cmd, shell=True) subprocess.check_call(cmd, shell=True, timeout=60 * 5) shutil.copyfile("out.json", "new.json") old = load_file("old.json") new = load_file("new.json") report = improvement_bench(old, new) else: subprocess.check_call(cmd, shell=True, timeout=60 * 5) stat = load_file("out.json") report = cmp_bench(stat) print_mkdown(report) ================================================ FILE: crates/bench/instruments.sh ================================================ #!/bin/bash set -euo pipefail if [ "$#" -lt "3" ] ; then echo "Usage: $0