ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SpacetimeDB C++ 模块库实战:用 C++20 在数据库内构建 WebAssembly 模块

SpacetimeDB C++ 模块库实战:用 C++20 在数据库内构建 WebAssembly 模块 SpacetimeDB C 模块库实战用 C20 在数据库内构建 WebAssembly 模块【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读SpacetimeDB C 模块库crates/bindings-cpp为 C 开发者提供了一套现代 C20 API用于编写编译为 WebAssembly 并在 SpacetimeDB 数据库内部运行的模块从而将应用服务端逻辑直接下沉到数据库中省去独立的业务服务层。本文围绕该库的官方 README 展开完整覆盖其功能清单、架构设计、环境准备、快速上手、构建与发布流程、全套宏 API 参考并结合仓库内的源码与示例模块ARCHITECTURE.md、REFERENCE.md、modules/module-test-cpp/src/lib.cpp、modules/sdk-test-cpp/src/lib.cpp做纵深讲解。读完本文你将能够独立创建一个 C SpacetimeDB 模块定义带约束的表、编写 reducer / view / procedure、构建出.wasm并发布到数据库同时理解其底层类型注册与校验机制。一、库定位与功能概览SpacetimeDB C 模块库的核心定位是用 C20 编写运行在数据库内部的 WebAssembly 模块。与传统“数据库 → 应用服务器 → 客户端”的三层架构不同模块直接在数据库内执行业务逻辑客户端通过订阅实时获得数据同步。该库提供了“生产就绪”的 C 绑定覆盖了完整的类型系统支持README 中列出的功能包括模块编译与发布源码经 Emscripten 编译为 WASM 后发布到 SpacetimeDB全部生命周期 reducerinit、client_connected、client_disconnected用户自定义 reducer支持不限数量的参数表注册与约束PrimaryKey、Unique、AutoInc插入 / 更新 / 删除操作基于类型安全的表访问器全部基础类型u8~u256、i8~i256、bool、f32、f64、string全部特殊类型Identity、ConnectionId、Timestamp、TimeDuration、Uuid、Result向量类型所有基础类型与特殊类型均可构成std::vectorT可选类型std::optionalT自定义结构体的 BSATN 序列化复杂枚举支持带载荷的变体枚举及正确的变体命名增强日志系统带文件 / 行号信息的多级别日志。进阶能力除基础功能外README 还列出了可直接使用的高级特性特性支撑宏 / API说明Btree 索引FIELD_Index配合range_from()、range_to()、range_inclusive()等实现优化查询范围查询range_queries.h完整的区间查询体系客户端可见性过滤SPACETIMEDB_CLIENT_VISIBILITY_FILTER行级安全基于 SQL 谓词控制客户端可见行定时 reducerSPACETIMEDB_SCHEDULE基于ScheduleAt字段的时间驱动执行过程procedureSPACETIMEDB_PROCEDURE返回值的纯函数可用显式事务访问数据库视图viewSPACETIMEDB_VIEW只读查询函数返回std::vectorT或std::optionalT字段访问器模式ctx.db[table_field]基于索引的高效操作这些能力在仓库中均有可运行的示例见modules/*-cpp/src/lib.cpp其中 modules/module-test-cpp/src/lib.cpp 覆盖了索引、范围查询、枚举、调度、HTTP 处理器等用法modules/sdk-test-cpp/src/lib.cpp 则是一个与 Rust / C# 测试模块完全等价的全量类型与操作测试模块2091 行覆盖每种基础类型、向量、可选、Result、唯一约束、主键表与调度表。二、架构设计混合编译期 / 运行期体系README 用四个要点概括了库的架构详细技术文档见 crates/bindings-cpp/ARCHITECTURE.md混合编译期 / 运行期系统Hybrid Compile-Time/Runtime SystemC20 concepts 在编译期完成校验__preinit__函数在 WASM 模块加载时执行运行期注册V9 类型注册系统统一类型注册具备全面的错误检测与循环引用防护名义类型系统Nominal Type System类型通过其声明的名字而非结构分析来标识通过SPACETIMEDB_STRUCT等宏显式注册多层校验Multi-Layer Validation静态断言 → 运行期约束检查 → 错误模块替换策略覆盖从编译到发布的全链路。2.1 优先级有序的初始化系统核心机制是带编号的__preinit__导出函数实现于 crates/bindings-cpp/src/internal/Module.cpp保证初始化顺序确定__preinit__01_ - 清理全局状态最先执行 __preinit__10_ - 字段注册 __preinit__19_ - 自增集成与定时 reducer __preinit__20_ - 表与生命周期 reducer 注册 __preinit__21_ - 字段约束 __preinit__25_ - 行级安全过滤 __preinit__30_ - 用户 reducer __preinit__40_ - 视图 __preinit__50_ - 过程 __preinit__99_ - 类型校验与错误检测最后执行为什么需要编号因为注册存在严格依赖表必须先于约束存在类型必须先于引用被注册而校验必须发生在所有注册完成之后。WASM 线性内存模型要求确定性初始化因此这种顺序是硬约束而非约定。2.2 类型注册与循环引用防护类型注册协调器是 V9Builder但所有类型处理都委托给统一的ModuleTypeRegistration系统crates/bindings-cpp/include/spacetimedb/internal/module_type_registration.h。其核心原则是只有用户自定义的结构体和枚举才进入类型空间typespace基础类型、数组、Optional 和特殊类型始终内联。注册流程依次检查基础类型 → 数组 → Option → 特殊类型 → 用户自定义类型注册并返回引用。循环引用通过types_being_registered_集合跟踪发现重复注册会立即设置全局错误标志并返回错误类型。2.3 错误模块替换策略在__preinit__99_validate_types中依次检查三类错误循环引用ERROR_CIRCULAR_REFERENCE_type、多个主键ERROR_MULTIPLE_PRIMARY_KEYS_table、类型注册错误ERROR_TYPE_REGISTRATION_message。一旦命中正常模块会被替换为包含无效类型引用的“错误模块”SpacetimeDB 解析该类型时即失败并把描述性错误名返回给开发者——这是从编译期到服务端的最后一道防线。三、环境准备构建 C 模块需要以下工具链依赖版本要求用途SpacetimeDB CLI最新版初始化、构建、发布、调用 reducer、执行 SQLEmscripten SDK (emsdk)最新版将 C 编译为 WebAssemblyCMake3.16库的CMakeLists.txt声明最低 3.15构建系统C 编译器支持 C20编译源码库本身的 CMake 配置见 crates/bindings-cpp/CMakeLists.txt静态库目标spacetimedb_cpp_library别名spacetimedb::spacetimedb_cpp_library强制cxx_std_20在 Emscripten 环境下会自动附加-O2 -fno-exceptions -ffunction-sections -fdata-sections -Wall -Wextra编译选项其中-fno-exceptions是 WASM 兼容性的关键——这也解释了为什么错误处理采用返回值而非异常。四、快速上手方式一spacetime init推荐# 创建一个新的 C 项目 spacetime init --lang cpp my-project cd my-project # 构建并发布 spacetime build -p ./spacetimedb spacetime publish -p ./spacetimedb my-databasespacetime init --lang cpp生成的项目结构为my-chat-module/spacetimedb/ ├── CMakeLists.txt ├── src/ └── lib.cpp └── .gitignore方式二手动搭建对已有项目在 C 模块中加入以下代码即可。这是 README 给出的完整最小示例涵盖了表、枚举、约束、reducer、生命周期 reducer、视图与过程#include spacetimedb.h using namespace SpacetimeDB; // 定义表结构 struct User { Identity identity; std::string name; std::string email; }; // 注册 BSATN 序列化 SPACETIMEDB_STRUCT(User, identity, name, email) // 注册为公共表 SPACETIMEDB_TABLE(User, users, Public) // 使用 FIELD_ 宏添加约束 FIELD_PrimaryKey(users, identity); FIELD_Unique(users, email); // 定义带命名空间限定的枚举 SPACETIMEDB_ENUM(UserRole, Admin, Moderator, Member) SPACETIMEDB_NAMESPACE(UserRole, Auth) // 客户端代码中显示为 Auth.UserRole // 用户自定义 reducer SPACETIMEDB_REDUCER(add_user, ReducerContext ctx, std::string name, std::string email) { User user{ctx.sender(), name, email}; // id 将自动生成 ctx.db[users].insert(user); LOG_INFO(Added user: name); return Ok(); } // 按主键删除用户 SPACETIMEDB_REDUCER(delete_user, ReducerContext ctx) { ctx.db[users_identity].delete_by_key(ctx.sender()); return Ok(); } // 生命周期 reducer可选 SPACETIMEDB_INIT(init, ReducerContext ctx) { LOG_INFO(Module initialized); return Ok(); } SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) { LOG_INFO(Client connected: ctx.sender().to_hex_string()); return Ok(); } SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) { LOG_INFO(Client disconnected: ctx.sender().to_hex_string()); return Ok(); } // 定义视图查询调用者自身的用户记录 SPACETIMEDB_VIEW(std::optionalUser, find_my_user, Public, ViewContext ctx) { // 使用索引字段按 identity 查找 return ctx.db[users_identity].find(ctx.sender()); } // 定义过程带返回值的纯函数 SPACETIMEDB_PROCEDURE(uint32_t, add_numbers, ProcedureContext ctx, uint32_t a, uint32_t b) { return a b; }4.1 手动搭建时的 CMake 配置若不用spacetime init参考 crates/bindings-cpp/REFERENCE.md 中的 CMake 配置库的CMakeLists.txt也支持MODULE_SOURCE与OUTPUT_NAME两个缓存变量默认分别为src/lib.cpp与libcmake_minimum_required(VERSION 3.16) project(my-module) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 指向仓库中的 C 绑定目录 set(SPACETIMEDB_CPP_LIBRARY_PATH path/to/crates/bindings-cpp) add_executable(lib src/lib.cpp) target_include_directories(lib PRIVATE ${SPACETIMEDB_CPP_LIBRARY_PATH}/include) add_subdirectory(${SPACETIMEDB_CPP_LIBRARY_PATH} spacetimedb_cpp_library) target_link_libraries(lib PRIVATE spacetimedb_cpp_library) # Emscripten 下的 WASM 设置 if(CMAKE_SYSTEM_NAME STREQUAL Emscripten) set_target_properties(lib PROPERTIES SUFFIX .wasm LINK_FLAGS -s STANDALONE_WASM1 ... ) endif()头文件入口为单头文件 crates/bindings-cpp/include/spacetimedb.h其中按模块系统、表与约束、reducer、procedure、视图等分组聚合了全部子头文件。五、构建与发布模块构建步骤# 进入模块目录 cd modules/your-module # 构建项目 spacetime build -p ./spacetimedb # 发布到 SpacetimeDB显式指定 wasm 路径 spacetime publish --bin-path ./spacetimedb/build/lib.wasm your-database-name # 或直接给目录自动检测 build/lib.wasm spacetime publish ./spacetimedb your-database-name自定义模块源码若要构建不同的源文件可覆盖 CMake 变量仓库的编译测试即采用此方式参考 crates/bindings-cpp/tests/compile/run-compile-tests.sh# 构建指定的测试模块 emcmake cmake -B build -DMODULE_SOURCEsrc/test_module.cpp -DOUTPUT_NAMEtest_module . cmake --build build # 生成 build/test_module.wasm完整的手动构建 / 发布命令# 使用 spacetime spacetime build -p . # 手动构建 emcmake cmake -B build . cmake --build build # 发布 spacetime publish . my-database # 或手动指定产物 spacetime publish --bin-path build/lib.wasm my-database启动本地实例后可先用 CLI 验证模块行为来自 crates/bindings-cpp/QUICKSTART.mdspacetime start # 启动本地 SpacetimeDB spacetime call my-db set_name Alice # 调用 reducer spacetime sql my-db SELECT * FROM user # 查询数据六、宏 API 参考表定义宏说明SPACETIMEDB_TABLE(Type, table_name, Public/Private)注册一张表SPACETIMEDB_STRUCT(Type, field1, field2, ...)为类型注册 BSATN 序列化可见性规则Public 表自动同步给订阅客户端Private 表仅 reducer 可访问、不同步给客户端。同一个结构体可注册为多张表例如一张私有错误日志表加一张公共审计日志表。枚举定义宏说明SPACETIMEDB_ENUM(EnumName, Value1, Value2, ...)定义简单枚举单元变体SPACETIMEDB_ENUM(EnumName, (Variant1, Type1), (Variant2, Type2), ...)定义带载荷的变体枚举SPACETIMEDB_NAMESPACE(EnumName, Namespace)为枚举添加命名空间限定变体枚举底层是std::variant因此每个载荷类型必须唯一——需要多个单元变体时用SPACETIMEDB_UNIT_TYPE(Name)创建唯一的空类型参见 modules/module-test-cpp/src/lib.cpp 中TestFFoo/TestFBar的做法这是与 C# SDK 直接复用Unit的差异点。Reducer宏说明SPACETIMEDB_REDUCER(name, ReducerContext ctx, ...)用户自定义 reducerSPACETIMEDB_INIT(name, ReducerContext ctx)模块初始化 reducer可选SPACETIMEDB_CLIENT_CONNECTED(name, ReducerContext ctx)客户端连接 reducer可选SPACETIMEDB_CLIENT_DISCONNECTED(name, ReducerContext ctx)客户端断开 reducer可选Reducer 的关键语义返回ReducerResult即Outcomevoid的类型别名成功用return Ok();失败用return Err(message);返回Err会触发整个事务回滚错误消息序列化后返回调用方不会导致 WASM 崩溃第一个参数必须是ReducerContext ctx其余参数由客户端传入且必须已注册序列化。ReducerContext提供的能力包括ctx.sender()调用方身份、ctx.timestamp当前时间戳、ctx.rng()确定性随机数以 reducer 时间戳微秒为种子、ctx.database_identity()、ctx.sender_auth()JWT 鉴权以及ctx.db[...]数据库访问。视图View宏说明SPACETIMEDB_VIEW(return_type, name, Public/Private, ViewContext ctx)只读查询函数SPACETIMEDB_VIEW(return_type, name, Public/Private, AnonymousViewContext ctx)匿名视图无发送方身份注意视图当前只支持 context 参数尚不支持额外的调用参数。过程Procedure宏说明SPACETIMEDB_PROCEDURE(return_type, name, ProcedureContext ctx, ...)返回值的纯函数要点直接返回类型本身不包裹在Outcome中可为任意 SpacetimeType基础类型、结构体、枚举、Unit等访问数据库需要显式事务ctx.WithTx()或ctx.TryWithTx()始终公开无访问控制。字段约束在表注册后应用宏说明FIELD_PrimaryKey(table_name, field)主键约束FIELD_PrimaryKeyAutoInc(table_name, field)自增主键FIELD_Unique(table_name, field)唯一约束FIELD_UniqueAutoInc(table_name, field)自增唯一字段FIELD_Index(table_name, field)索引btree加速查询与范围操作FIELD_IndexAutoInc(table_name, field)自增索引字段FIELD_AutoInc(table_name, field)仅自增无其他约束FIELD_NamedMultiColumnIndex(table, index_name, col1, col2)多列 btree 索引FIELD_Default(table, field, value)字段默认值迁移 / 加列场景约束的合法类型有硬性要求见 crates/bindings-cpp/REFERENCE.md约束类型允许的类型PrimaryKey整数、bool、string、Identity、ConnectionId、Timestamp、枚举Unique同 PrimaryKeyIndex同 PrimaryKeyAutoInc仅整数类型这些限制在编译期由 C20 concepts如FilterableValue、AutoIncrementable定义于 crates/bindings-cpp/include/spacetimedb/table_with_constraints.h和static_assert强制执行违反会得到带字段名和指引的清晰编译错误。自增回调机制使用自增字段时insert()会自动返回带生成 ID 的行对象。底层流程ARCHITECTURE.md 有完整描述insert()序列化并发送行 → 服务端生成自增值 → 服务端仅回传生成的列值BSATN 格式→ SDK 调用注册在__preinit__19_的集成函数把生成值写回原行 →insert()返回填充完整的行。因此插入后立即可用生成 IDUser user{0, Bob, true}; // id0 是占位符将被自动生成 User inserted ctx.db[user].insert(user); LOG_INFO(Created user with ID: std::to_string(inserted.id));支持多个自增字段共存例如FIELD_PrimaryKeyAutoInc与FIELD_UniqueAutoInc同时存在时所有生成值都会被集成。七、日志系统LOG_DEBUG(Debug message); LOG_INFO(Info message); LOG_WARN(Warning message); LOG_ERROR(Error message); LOG_PANIC(Fatal error message); // 带计时 { LogStopwatch timer(Operation name); // ... 需要计时的代码 ... } // 离开作用域时自动输出耗时实现位于 crates/bindings-cpp/include/spacetimedb/logger.h日志包含文件 / 行号等源码位置信息。日志级别还可通过模块源码顶部的#define STDB_LOG_LEVEL覆盖modules/module-test-cpp/src/lib.cpp 中即设置为TRACE。八、数据库访问模式详解C 绑定使用独特的双访问器模式QUICKSTART.md 称之为 “unique accessor pattern”ctx.db[tableName]—— 表访问用于迭代和基础操作如ctx.db[user].insert(...)、for (const auto row : ctx.db[user])、ctx.db[user].count()ctx.db[tableName_fieldName]—— 字段访问器用于基于索引的高效操作如ctx.db[user_identity].find(...)、delete_by_key(...)、filter(...)。操作表访问字段访问索引Insertinsert(row)—Delete手动迭代delete_by_key(key)Updateupdate(row)update(row)查询迭代filter(value)范围查询对已建FIELD_Index的字段可进行范围查询头文件 crates/bindings-cpp/include/spacetimedb/range_queries.hauto range1 range_from(25); // 25.. 25 auto range2 range_to(30); // ..30 30 auto range3 range(20, 35); // 20..35 20, 35 auto range4 range_inclusive(20, 35); // 20..35 20, 35 auto range5 range_to_inclusive(30); // ..30 auto range6 range_fullint(); // 全量 bool in_range range4.contains(25); // true // 对索引字段过滤字符串区间同样可用 for (const auto product : ctx.db[product_item_price].filter(price_range)) { LOG_INFO(Product in range: product.name); }modules/module-test-cpp/src/lib.cpp 的test_btree_index_argsreducer 对整数、多列坐标、字符串三类区间做了完整验证并对比了“基于索引的范围过滤”与“全表手动过滤”的结果一致性。九、类型系统细节基础类型与容器C 绑定支持全部标准整数 / 浮点类型外加 SpacetimeDB 专属的大整数类型u128、u256、i128、i256以及std::string、std::vectorT、std::optionalT、ResultT, E。注意类型必须精确匹配例如用uint32_t而非intQUICKSTART.md 的故障排查一节特别强调了这一点。命名空间限定系统SPACETIMEDB_NAMESPACE(EnumName, Prefix)是一个纯编译期特性它通过模板特化SpacetimeDB::detail::namespace_infoT存储常量字符串LazyTypeRegistrar在注册时用if constexpr检测命名空间并拼接限定名如Auth.UserRole。客户端代码生成器据此在 TypeScript、C#、Rust 客户端中组织类型而服务端 C 代码仍使用未限定的名字。优点是零运行期开销、可选且向后兼容详见 crates/bindings-cpp/ARCHITECTURE.md 的命名空间章节。十、已知限制README 明确列出了当前版本的边界类型系统非常大的类型组合可能超过 WASM 内存限制复杂递归类型引用需要仔细安排注册顺序。数据库操作基于索引的操作使用字段访问器ctx.db[table_field].delete_by_key(value)表约束由服务端声明并强制实施通过字段访问器支持 insert / delete / update。高级特性FIELD_Index创建 btree 索引以支持高效范围查询支持range_from()、range_to()、range_inclusive()等完整范围查询体系行级安全通过SPACETIMEDB_CLIENT_VISIBILITY_FILTER实现迁移能力有限仅支持自动添加表SQL 执行仅能通过 CLIspacetime sql使用模块内部不可执行 SQL。此外ARCHITECTURE.md 也提示其内容中仍保留少量历史实现描述当前实现已精简为生产就绪状态。十一、示例模块与测试README 指向modules/*-cpp/src/目录下的示例modules/module-test-cpp/src/lib.cpp —— 与 Rustmodule-test等价约束 / 索引 / 枚举 / 视图 / 调度 reducer / JWT 鉴权 / 过程 / HTTP 处理器全覆盖并包含大量范围查询验证modules/sdk-test-cpp/src/lib.cpp —— 全量类型与操作测试模块与 Rust、C# SDK 测试模块完全等价每种基础类型与特殊类型的单值表、向量表、可选表、Result 表、唯一约束表、主键表以及对应的 insert / delete / update reducer。测试体系方面仓库还提供了类型隔离测试crates/bindings-cpp/tests/type-isolation-test/含error_circular_ref.cpp、error_multiple_pk.cpp、error_autoinc_non_integer.cpp等负向用例与module01~module12的正向用例编译期校验测试crates/bindings-cpp/tests/compile/cases/查询构建器编译与 SQL 测试crates/bindings-cpp/tests/query-builder-compile/、crates/bindings-cpp/tests/query-builder-sql/。这些用例从编译期概念校验、运行期注册校验到 SQL 查询语义多个层面印证了本文所述的架构设计。十二、常见问题排查QUICKSTART.md 给出的排查要点构建错误确保 Emscripten SDK 为最新并使用emcmake cmake模块找不到检查 SpacetimeDB 是否正在运行类型错误C 类型必须精确匹配用uint32_t而不是int约束冲突约束由数据库强制执行主键重复会使 reducer 失败返回Err触发回滚。结语SpacetimeDB C 模块库以“编译期概念校验 运行期__preinit__注册 名义类型系统 多层错误检测”的混合架构在 WASM 环境约束无异常、16MB 初始内存、线性初始化下提供了接近 Rust 绑定体验的类型安全开发流程。配合仓库内的完整示例与测试模块开发者可以从一个最小聊天模块起步逐步掌握表约束、范围查询、行级安全、定时执行与过程调用等全部能力将 C 后端逻辑直接搬进数据库内部运行。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表