Files
Genarrative/server-rs/crates/spacetime-client
suzmii f0cad9dddc
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m28s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m26s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m41s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m56s
Project CI / Backend tests (pull_request) Successful in 9m4s
Project CI / Repository checks (pull_request) Successful in 7m13s
Project CI / Native shell tests (pull_request) Successful in 10m16s
Project CI / Frontend tests (pull_request) Successful in 2m26s
合并 origin/master(52c83cc2a,248 个提交)到 feat/game-purchase
- 同步 master 侧 248 个提交:会员与泥点计费(PR #588 / #603)、创作者主页与关注粉丝(PR #638)、运行页刷新真重载(PR #640)、AGC 模板 templateVersion 递增(PR #643)等;确认 master 未触碰发行网关与 `api-server/src/modules/game_distribution.rs`。
- 冲突 1(server-rs/crates/module-runtime/src/domain.rs:1157-1162)`RuntimeProfileWalletLedgerSourceType` 两侧各自在末尾追加变体:保留 master 的 `MembershipUpgradeGrant` 为索引 16(线上已落库口径),本分支 `GamePurchase` 顺延为索引 17,并保留其「每账号每游戏一次扣费」注释。
- 冲突 2(server-rs/crates/module-runtime/src/domain.rs:1184-1188)`as_str()` 同时保留 `membership_upgrade_grant` 与 `game_purchase` 两个分支。
- 冲突 3(server-rs/crates/spacetime-client/src/active/mapper/runtime_profile.rs:95-101)bindings→领域枚举映射同时保留 `MembershipUpgradeGrant` 与 `GamePurchase` 两支,按新索引顺序排列。
- 冲突 4(server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_wallet_ledger_source_type_type.rs:43-47)生成枚举按模块源码顺序合并为 …`MembershipUpgradeGrant`、`GamePurchase`;随后 `npm run spacetime:generate` 复核该文件与生成结果逐字节一致。
- 冲突 5(server-rs/crates/api-server/src/admin.rs:4849-4853)sats 索引映射改为 `16 => membership_upgrade_grant` 与 `17 => game_purchase` 两条并存。
- 冲突 6(server-rs/crates/api-server/src/admin.rs:6368-6461)索引断言并入 master 的 `[16] => membership_upgrade_grant`,并把 master 的「17 尚未映射」占位断言替换为 `[17] => game_purchase`;本分支的后台消耗统计两个用例原样保留。
- 冲突 7(server-rs/crates/api-server/src/runtime_profile.rs:219-226)来源文案格式化同时保留 `MembershipUpgradeGrant` 与 `GamePurchase` 两支,注释随 `GamePurchase` 保留。
- 冲突 8(docs/project-memory/shared-memory/decision-log.md:3-24)两条决策记录并列保留:本分支 2026-10-05「播放会话前缀在三处入口清空 Cookie」在前,master 2026-10-03「每日免费发放额为 0 时前端隐藏该池」在后。
- 保住 master 侧:发行响应 ETag/304/gzip(`release_asset_response_with_cache` 五参形态与 `release_asset_etag` / `if_none_match_matches` / `accepts_gzip_encoding` / `gzip_release_asset` / `release_asset_not_modified_response`)、共享加载面 `PlatformGameLoadingSurface` 及其使用方、三池钱包与会员档位目录改造。
- 保住本分支侧:付费作品 404 守卫(`price_mud_points > 0` 且位于 `load_package` 之前)、购买与播放会话网关路由、nginx×3 / Pingora / Vite 的播放会话 Cookie 清空与 `nginx-route-parity.matrix.json` 登记、viewer 感知公开详情、后台审核价格、`PlatformGamePricingField` 共享定价组件与两端接入、AGC 项目版本只读 + 平台派发下一版本号、schema 与迁移白名单、钱包来源 `game_purchase`。
- 验证:`git merge-base --is-ancestor origin/master HEAD` 返回 0;`check:encoding`、`check:doc-index`、`check:spacetime-schema`、`check:server-rs-ddd`、`check:rustfmt`、`check:npm-workspaces`、`check:game-distribution-dto-parity`、`check:game-distribution-price-limit-parity`、`check:pingora-route-parity`、`check:nginx-spa-routes`、根/admin-web/AGC typecheck、`git diff --check` 全绿。
- 测试:`cargo test -p module-game-distribution` 30 passed,`cargo test -p api-server game_distribution` 61 passed,`cargo test -p spacetime-module game_distribution` 7 passed;定向 vitest 28 个文件 252 个用例全通过。
- `npm run spacetime:generate` 除 11 个 procedure 文件的纯空白格式差异外无任何语义改动(`git diff -w` 为空),已保留 master 的生成产物形态,提交内无 schema/绑定语义变化。
2026-10-06 13:12:32 +08:00
..

spacetime-client 共享 package 说明

日期:2026-05-01

1. package 职责

spacetime-client 是 SpacetimeDB 客户端适配 package,后续负责:

  1. 生成 bindings 后的客户端访问封装
  2. Axum 与各模块对 reducer、view、订阅的调用适配
  3. 身份透传、连接配置与基础错误处理适配

在 DDD 重构中,本 package 只承接 WP-SC Spacetime Client:

  1. 把 SpacetimeDB 生成绑定转换成 api-server 可消费的 typed facade。
  2. 把 row snapshot / procedure result 转换成 BFF record。
  3. 统一 SDK 调用错误、业务 procedure 错误、缺失快照错误和超时错误。
  4. 不承载领域规则,不直接定义 table / reducer / procedure,不替代 spacetime-module。

当前约束见 ../../../docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md。

2. 当前完成口径

当前目录已不再只是占位。WP-SC Spacetime Client 在当前稳定 SpacetimeDB facade 范围内已经完成收尾:

  1. 通过 npm run spacetime:generate -- --rust-only 生成并纳管公开 Rust bindings。
  2. DbConnection 调用连接池、单一缓存读连接、握手等待、超时和断线清理已封装在 SpacetimeClient 内部;调用池不订阅 read model,HTTP 角色额外使用一条共享连接保存订阅行缓存。
  3. 已稳定的 assets、auth、AI task、Big Fish、Custom World、Puzzle、Runtime/Profile/Save、Story session、combat、inventory、NPC facade 均通过 typed 方法对外暴露。
  4. 生成绑定到 BFF record / module record 的 row snapshot mapper 已集中在 mapper.rs。
  5. SDK 调用错误、reducer 业务错误、procedure 业务错误、缺快照错误和本地输入校验错误已统一收口到 SpacetimeClientError helper。
  6. Story runtime projection source 已复用 runtime inventory typed facade,读取投影不再只依赖 runtime snapshot 中的历史背包 JSON 副本。
  7. Story runtime 投影读取会对历史 currentStory.options 做兼容推断:若旧快照缺少 scope,仍会按 functionId 通过 module-runtime-story 的 option helper 还原为 story / combat / npc 作用域,避免旧存档把读取链路卡死。

confirm_asset_object_and_return 与 bind_asset_object_to_entity_and_return 的调用必须等到 SDK on_connect 回调后再发起。DbConnection::build() 只代表 WebSocket 已经初始化,不代表 SpacetimeDB 身份握手完成;如果过早调用 procedure,本地联调会表现为连接建立但请求长期没有回调,最终等到 idle timeout。

后续新增工作只随 WP-ST 新 table / reducer / procedure 或 row shape 稳定后按领域增量接入,不再把整个 WP-SC 包保持为进行中状态。新增 facade 时必须继续满足:

  1. 不手写 module_bindings 生成物。
  2. 不在 spacetime-client 内新增领域规则。
  3. procedure / reducer shape 稳定后再接 typed facade。
  4. 错误映射继续使用 SpacetimeClientError helper。
  5. mapper 测试或 facade 定向测试随新增场景补齐。

2.1 module_bindings 生成物约束

src/module_bindings 目录下的 Rust 文件统一视为 SpacetimeDB CLI 生成产物,后续维护必须遵守:

  1. 只允许通过仓库根目录 npm run spacetime:generate -- --rust-only 刷新,不允许手工修改。
  2. 不生成私有表绑定,不追加 --include-private;如后端需要读取私有表,应先在 api-server 或模块层补明确 contract,而不是让客户端 crate 直接依赖私有表结构。
  3. 不允许手工对该目录执行散装 rustfmt;若 SpacetimeDB CLI 已生成文件但自身 formatter 在 Windows 下失败,只能由 scripts/generate-spacetime-bindings.mjs 在短临时目录中分批 rustfmt 后同步。
  4. src/lib.rs 已通过 #[rustfmt::skip] pub mod module_bindings; 显式阻止 workspace 级 cargo fmt 继续递归格式化该目录。
  5. Windows 下 SpacetimeDB CLI 2.1.0 的生成后 formatter 可能因为一次性传入过多 Rust 文件路径而失败;仓库脚本会先输出到短临时目录,必要时接管分批格式化,再同步回本目录。

2.1.1 绑定缺文件恢复流程

若 mod.rs 已声明 *_table 模块,但目录内缺少对应 *_table.rs 文件,说明 Rust bindings 刷新不完整。不要手工补 generated code,统一在仓库根目录执行:

npm.cmd run spacetime:generate -- --rust-only

脚本内部会使用 --no-config:仓库根目录的 spacetime.json 同时配置了 TypeScript 与 Rust 两个生成目标,直接追加 --lang / --out-dir 会触发 SpacetimeDB CLI 的多目标参数冲突。

生成后用以下命令确认 mod.rs 声明的模块都有落盘文件:

$modFile = 'server-rs\crates\spacetime-client\src\module_bindings\mod.rs'
$dir = 'server-rs\crates\spacetime-client\src\module_bindings'
$mods = Select-String -Path $modFile -Pattern '^pub mod ([a-zA-Z0-9_]+);' |
  ForEach-Object { $_.Matches[0].Groups[1].Value }
$missing = @()
foreach ($m in $mods) {
  if (-not (Test-Path (Join-Path $dir ($m + '.rs')))) {
    $missing += $m
  }
}
if ($missing.Count -eq 0) { 'missing module files: 0' } else { $missing }

最后至少执行:

cargo check -p spacetime-client --manifest-path server-rs\Cargo.toml

3. 边界约束

  1. spacetime-client 只承接 SpacetimeDB 客户端访问适配,不承接具体业务模块的规则实现。
  2. 业务状态真相仍由 apps/spacetime-module 管理,业务编排由各模块 package 与 apps/api-server 承担。
  3. 不允许把 reducer、view、订阅调用细节重新散落到多个业务模块里各自实现。
  4. 新增 facade 必须等待对应 spacetime-module facade 稳定后再接,不提前假设 row shape。
  5. src/module_bindings/** 是生成产物,只能通过 SpacetimeDB CLI 生成流程刷新。