feat: add spacetimedb json migration tooling
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-27 14:54:26 +08:00
parent ded6f6ee2a
commit 9a79494c68
13 changed files with 1532 additions and 2 deletions

View File

@@ -0,0 +1,196 @@
# SpacetimeDB JSON 字符串迁移 procedure 设计
## 背景
`spacetime sql` 只能稳定读取 public 表或数据库 owner 可见表。当前 `ai_result_reference` 等运行真相表保持 private直接 SQL 导出会遇到 `no such table` 或 private table 提示,不能作为跨服务器迁移的稳定方案。
SpacetimeDB reducer 必须保持确定性不能访问文件系统和网络。procedure 可以返回数据,也可以在事务中读取 private 表,因此迁移改为:
1. `spacetime-module` 内的导出 procedure 读取迁移白名单表,并直接返回迁移 JSON 字符串。
2. Node 运维脚本默认通过 `spacetime call` 调用导出 procedure把返回的 JSON 字符串写入本地文件。
3. Node 运维脚本读取本地 JSON 文件内容,并通过 HTTP request body 作为字符串参数传给导入 procedure。
4. 导入 procedure 校验 JSON 与表白名单后,在事务中写入目标数据库。
procedure 不再访问 HTTP 文件桥,也不接收部署机本地文件路径。这样可以避开 SpacetimeDB 对 private/special-purpose 地址的 HTTP 访问限制,并避免把 private 表内容通过临时 HTTP 服务转发。
`spacetime login show --token` 输出的是 CLI 登录 token不是 HTTP `/v1/database/.../call` 所需的数据库连接 token。运维脚本默认走 CLI 登录态,迁移时不要把 CLI token 传给 `--token`;只有显式传 `--use-http` 时才需要数据库连接 token。
## 接口
### 迁移操作员授权
迁移 procedure 会读取并写入 private 表,不能对任意登录身份开放。模块内新增私有表 `database_migration_operator` 作为迁移操作员白名单:
- `operator_identity`: 被授权调用迁移 procedure 的 SpacetimeDB identity。
- `created_at`: 授权写入时间。
- `created_by`: 发起授权的 identity。
- `note`: 运维备注,只用于区分来源、环境或临时用途。
`database_migration_operator` 只控制迁移 procedure 调用权限,不会被导出或导入,避免把源库的运维权限复制到目标库。
首次授权时,操作员表为空,必须通过编译进模块的 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET` 引导密钥授权第一位操作员。发布脚本会在构建或发布 SpacetimeDB 模块时自动生成一份强随机引导密钥、注入 wasm 编译环境,并在控制台显示;运维人员必须记录对应数据库本次发布输出的密钥。表内已经存在操作员后,后续授权与撤销只能由已有操作员发起;此时不再接受引导密钥越权扩权。
新增 procedure
- `authorize_database_migration_operator`: 授权或更新迁移操作员备注。
- `revoke_database_migration_operator`: 撤销迁移操作员。
运维流程:
```bash
npm run spacetime:publish:maincloud -- --database <database>
# 控制台会输出:
# [spacetime:maincloud] 迁移引导密钥: <本次发布随机密钥>
```
发布完成后,在同一台机器上用当前 `spacetime login` 身份授权操作员:
```bash
node scripts/spacetime-authorize-migration-operator.mjs \
--server maincloud \
--database xushi-p4wfr \
--bootstrap-secret <本次发布随机密钥> \
--operator-identity <identity-hex> \
--note "2026-04-27 migration"
```
迁移完成后可以撤销临时操作员:
```bash
node scripts/spacetime-revoke-migration-operator.mjs \
--server maincloud \
--database xushi-p4wfr \
--operator-identity <identity-hex>
```
生产环境建议迁移完成后用 `--no-migration-bootstrap-secret` 重新发布一个未设置 `GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET` 的模块版本,避免引导密钥长期留在 wasm 中。
### 发布脚本密钥行为
当前所有会构建或发布 `spacetime-module` 的脚本默认都会生成并显示迁移引导密钥:
- `npm run spacetime:publish:maincloud`:在本机 `cargo build` 前生成密钥,控制台输出 `[spacetime:maincloud] 迁移引导密钥: ...`
- `npm run dev:rust`:在本地 `spacetime publish --module-path` 前生成密钥,控制台输出 `[dev:rust] 迁移引导密钥: ...`
- `npm run deploy:rust:remote`:在构建发布包 wasm 前生成密钥,控制台输出 `[deploy:rust] 迁移引导密钥: ...`,并把同一份密钥写入发布包根目录的 `migration-bootstrap-secret.txt`。服务器执行 `./start.sh` 发布 wasm 时也会再次显示该文件里的密钥。
如果迁移完成后不希望 wasm 继续携带引导密钥,重新发布时传 `--no-migration-bootstrap-secret`。远端发布包若使用 `--skip-spacetime-build`,必须同时传 `--no-migration-bootstrap-secret`,否则脚本会拒绝生成一个无法注入旧 wasm 的新密钥。
### 导出
`export_database_migration_to_file(ctx, input)`
输入字段:
- `include_tables`: 可选表名白名单。为空时导出当前实现支持的全部迁移表。
返回字段:
- `ok`: 是否成功。
- `schema_version`: 迁移 JSON 结构版本。
- `migration_json`: 成功时包含完整迁移 JSON 字符串,失败时为空。
- `table_stats`: 表级导出统计。
- `error_message`: 失败原因。
### 导入
`import_database_migration_from_file(ctx, input)`
输入字段:
- `migration_json`: 导出 procedure 生成的完整迁移 JSON 字符串。
- `include_tables`: 可选表名白名单。为空时导入文件内所有支持表。
- `replace_existing`: 是否先清空目标表。跨服务器全量迁移必须为 `true`
- `dry_run`: 只解析和统计,不写表。
返回字段:
- `ok`: 是否成功。
- `schema_version`: 迁移 JSON 结构版本。
- `migration_json`: 导入场景恒为空,避免重复回传大 JSON。
- `table_stats`: 表级导入或跳过统计。
- `error_message`: 失败原因。
保留 `export_database_migration_to_file` / `import_database_migration_from_file` 名称,是为了减少已经记住的 procedure 名变更;语义上不再代表 module 直接读写文件。
## Node 脚本
本机导出时,先确保本机 SpacetimeDB 服务和源数据库可访问,然后授权本机调用身份:
```bash
node scripts/spacetime-authorize-migration-operator.mjs \
--server dev \
--database xushi-p4wfr \
--bootstrap-secret <本机源库发布时输出的随机密钥> \
--operator-identity <本机 spacetime login show 中的 identity> \
--note "local export"
```
导出脚本负责调用本机源库 procedure 并保存返回 JSON
```bash
node scripts/spacetime-export-migration-json.mjs \
--server dev \
--database xushi-p4wfr \
--out tmp/spacetime-migrations/source-2026-04-27.json
```
`tmp/spacetime-migrations/source-2026-04-27.json` 复制到服务器后,在服务器上登录目标 SpacetimeDB并授权服务器侧调用身份
```bash
node scripts/spacetime-authorize-migration-operator.mjs \
--server maincloud \
--database xushi-p4wfr \
--bootstrap-secret <服务器目标库发布时输出的随机密钥> \
--operator-identity <服务器 spacetime login show 中的 identity> \
--note "server import"
```
导入脚本负责读取服务器本地文件并把 JSON 字符串传入目标库 procedure
```bash
node scripts/spacetime-import-migration-json.mjs \
--server maincloud \
--database xushi-p4wfr \
--in tmp/spacetime-migrations/source-2026-04-27.json \
--replace-existing
```
正式导入前建议先加 `--dry-run`,确认 JSON 可解析、版本匹配、表名都在迁移白名单内。
如需分批迁移,可用逗号分隔表名:
```bash
node scripts/spacetime-export-migration-json.mjs \
--database xushi-p4wfr \
--out tmp/spacetime-migrations/ai.json \
--include ai_task,ai_task_stage,ai_text_chunk,ai_result_reference
```
`--server` 支持 `dev``local``maincloud`,也可以直接传 SpacetimeDB 服务器 URL。脚本默认走 `spacetime call`,使用当前机器的 CLI 登录态。数据库名可通过 `--database``GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE``GENARRATIVE_SPACETIME_DATABASE` 提供。
授权脚本额外支持:
- `--bootstrap-secret``GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET`
- `--operator-identity``GENARRATIVE_SPACETIME_MIGRATION_OPERATOR_IDENTITY`
- `--note`
## 表范围
首版覆盖当前 private table 报错相关与主运行真相表:
- 认证:`auth_store_snapshot``user_account``auth_identity``refresh_session`
- AI`ai_task``ai_task_stage``ai_text_chunk``ai_result_reference`
- 运行存档与账户投影:`runtime_snapshot``runtime_setting``user_browse_history``profile_dashboard_state``profile_wallet_ledger``profile_invite_code``profile_referral_relation``profile_played_world``profile_membership``profile_recharge_order``profile_save_archive`
- RPG 运行真相:`player_progression``chapter_progression``npc_state``story_session``story_event``inventory_slot``battle_state``treasure_record``quest_record``quest_log`
- 自定义世界:`custom_world_profile``custom_world_session``custom_world_agent_session``custom_world_agent_message``custom_world_agent_operation``custom_world_draft_card``custom_world_gallery_entry`
- 资产索引:`asset_object``asset_entity_binding`
- 拼图:`puzzle_agent_session``puzzle_agent_message``puzzle_work_profile``puzzle_runtime_run`
- 大鱼:`big_fish_creation_session``big_fish_agent_message``big_fish_asset_slot``big_fish_runtime_run`
后续新增 SpacetimeDB 表时,必须同步把表加入迁移白名单与本文档。
## 风险与限制
迁移 JSON 作为 procedure 返回值和 HTTP request body 传递,会受 SpacetimeDB 调用响应体、请求体以及中间代理大小限制。数据量较大时,先按 `include_tables` 分批迁移;若单表本身过大,再补充分片 procedure而不是恢复 HTTP 文件桥。
`spacetime call` 在 PowerShell 中手写 JSON 容易被剥掉双引号。推荐使用仓库里的 Node 脚本,由脚本直接走 HTTP API避免 shell 二次处理和命令行长度限制。