Close DDD refactor and remove generated asset proxy
This commit is contained in:
5
server-rs/Cargo.lock
generated
5
server-rs/Cargo.lock
generated
@@ -1637,6 +1637,11 @@ dependencies = [
|
||||
name = "module-story"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"module-combat",
|
||||
"module-inventory",
|
||||
"module-progression",
|
||||
"module-quest",
|
||||
"module-runtime-item",
|
||||
"serde",
|
||||
"shared-kernel",
|
||||
"spacetimedb",
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
1. `crates/spacetime-module` 的表、reducer、view 聚合入口
|
||||
2. `module-auth` 的身份表、JWT 与 refresh cookie 主链
|
||||
3. `platform-oss` 的浏览器直传签名、旧 `/generated-*` 前缀映射与对象 URL 解析能力
|
||||
3. `platform-oss` 的浏览器直传签名、旧 `/generated-*` 前缀到 OSS object key 的映射与对象 URL 解析能力;`/generated-*` 不再作为可裸读 HTTP 路由
|
||||
|
||||
当前本地脚本补充说明:
|
||||
|
||||
|
||||
@@ -46,12 +46,10 @@
|
||||
22. 接入 `POST /api/assets/sts-upload-credentials` 禁用式 STS 写权限 contract
|
||||
23. 接入 `custom-world-library`、`custom-world-gallery` 与 agent `publish_world` 首批 Axum facade
|
||||
24. 接入 custom world agent `session create / session snapshot` Axum facade
|
||||
25. 接入旧 `runtime story` 兼容接口:
|
||||
- `POST /api/runtime/story/state/resolve`
|
||||
- `GET /api/runtime/story/state/{session_id}`
|
||||
- `POST /api/runtime/story/actions/resolve`
|
||||
- `POST /api/runtime/story/initial`
|
||||
- `POST /api/runtime/story/continue`
|
||||
25. 旧 `runtime story` 兼容接口已下线并保持未挂载;运行时故事写链路改为:
|
||||
- `POST /api/story/sessions/runtime`
|
||||
- `GET /api/story/sessions/{story_session_id}/runtime-projection`
|
||||
- `POST /api/story/sessions/{story_session_id}/actions/resolve`
|
||||
26. 接入 `POST /api/assets/character-visual/generate`
|
||||
27. 接入 `GET /api/assets/character-visual/jobs/{task_id}`
|
||||
28. 接入 `POST /api/assets/character-visual/publish`
|
||||
@@ -62,7 +60,8 @@
|
||||
33. 接入 `POST /api/assets/character-animation/generate`
|
||||
34. 接入 `GET /api/assets/character-animation/jobs/{task_id}`
|
||||
35. 接入 `POST /api/assets/character-animation/publish`
|
||||
36. 接入旧 `/generated-character-drafts/*`、`/generated-characters/*`、`/generated-animations/*`、`/generated-custom-world-scenes/*`、`/generated-custom-world-covers/*`、`/generated-qwen-sprites/*` 到 OSS 私有读代理
|
||||
36. 下线旧 `/generated-*` 资产直读代理;生成资产读取统一走 `/api/assets/read-url` 或 asset object projection
|
||||
37. 下线旧 `/api/custom-world/*` 非 runtime 前缀和 `/api/runtime/puzzle/runs/local-next-level`,并用路由回归测试确认这些旧入口保持 `404`
|
||||
|
||||
后续与本 crate 直接相关的任务包括:
|
||||
|
||||
@@ -87,12 +86,13 @@
|
||||
19. [x] 接入 `/api/assets/sts-upload-credentials`
|
||||
20. [x] 接入 `custom world library / gallery / publish_world` 首批 facade
|
||||
21. [x] 接入 `custom world agent session create / snapshot` facade
|
||||
22. [x] 接入旧 `runtime story` compat facade
|
||||
22. [x] 下线旧 `runtime story` compat facade,并接入 story session scoped runtime story 写读入口
|
||||
23. [x] 接入 `character-visual generate / jobs / publish` 第一批 OSS 主链兼容 facade
|
||||
24. [x] 接入 `character-animation templates / import-video` 第一批 OSS 草稿兼容 facade
|
||||
25. [x] 接入 `character-workflow-cache get / save` 第一批 OSS JSON 草稿兼容 facade
|
||||
26. [x] 接入 `character-animation generate / jobs / publish` 第一批 OSS 主链兼容 facade
|
||||
27. [x] 接入旧 `/generated-*` 路径到 OSS 私有读同源代理
|
||||
27. [x] 下线旧 `/generated-*` 路径 OSS 私有读同源代理
|
||||
28. [x] 下线旧 `/api/custom-world/*` 非 runtime 前缀和 Puzzle `local-next-level` 兼容入口
|
||||
|
||||
当前 tracing 约定:
|
||||
|
||||
@@ -161,10 +161,12 @@
|
||||
12. 当前微信回调不会把第三方 token 直接透传给前端或 SpacetimeDB,而是统一换成系统签发的 JWT。
|
||||
13. 当前 `/api/assets/sts-upload-credentials` 按“服务器上传、Web 只下载”口径固定返回 `403`,不向浏览器下发 OSS 写权限。
|
||||
14. 当前 `/api/runtime/custom-world/agent/sessions` 与 `/api/runtime/custom-world/agent/sessions/{session_id}` 只提供 deterministic session 骨架与 snapshot 读取,不承诺 message submit、operation query、card detail 的完整能力。
|
||||
15. 当前 `/api/runtime/story/*` 已在 Rust 侧补齐 compat handler,但内部仍是 `runtime_snapshot` 驱动的兼容桥与确定性动作编排,不应误判为真正的 SpacetimeDB `resolve_story_action` 真相链已完成。
|
||||
15. 当前 `/api/runtime/story/*` 不再挂载;runtime story 开局、读取和动作结算通过 `/api/story/sessions/runtime`、`/api/story/sessions/{story_session_id}/runtime-projection` 与 `/api/story/sessions/{story_session_id}/actions/resolve` 进入 BFF,并由 `module-runtime-story`、story session 和 runtime snapshot 投影共同闭合。
|
||||
16. 当前 `/api/assets/character-visual/*` 第一批只保证旧接口 contract、OSS 草稿/正式对象、`asset_object` 与 `asset_entity_binding` 主链可用;真实图片模型、workflow cache 与本地角色覆盖写回仍在后续阶段。
|
||||
17. 当前 `/api/assets/character-animation/import-video` 第一批只接受 `data:video/*;base64,...` 并写入 OSS 草稿区,不读取旧本地 `public/` 路径,也不创建正式 `asset_object`。
|
||||
18. 当前 `/api/assets/character-workflow-cache/*` 第一批只把工作流 JSON 草稿写入 OSS,不迁移历史本地缓存,也不创建正式 `asset_object`。
|
||||
19. 当前 `/api/assets/character-animation/generate` 第一批只用 Rust 占位产物打通 `AiTaskService + OSS` 草稿链;`image-sequence` 写 SVG 帧,视频类策略优先复用参考视频或仓库内可播放占位视频,不代表真实上游视频模型已完成迁移。
|
||||
20. 当前 `/api/assets/character-animation/publish` 会把前端提交帧、动作级 manifest 与总 manifest 写入 OSS,并只把总 manifest 确认为 `asset_object` 后绑定到 `character / animation_set`。
|
||||
21. 当前旧 `/generated-*` 读取兼容层只代理受支持 generated 前缀到 OSS 私有读签名,不回退仓库 `public/`,Stage 1 不支持视频 Range 分片。
|
||||
21. 当前旧 `/generated-*` 直读兼容层已下线;`/generated-*` 只作为 `legacyPublicPath` / OSS object key 标识,读取必须通过 `/api/assets/read-url` 或业务投影中的签名读 URL。
|
||||
22. 当前旧 `/api/custom-world/entity`、`/api/custom-world/scene-npc`、`/api/custom-world/scene-image`、`/api/custom-world/cover-image`、`/api/custom-world/cover-upload` 不再挂载,RPG 创作资产入口统一使用 `/api/runtime/custom-world/*`。
|
||||
23. 当前旧 `/api/runtime/puzzle/runs/local-next-level` 不再挂载,正式 Puzzle 运行态只通过 `/api/runtime/puzzle/runs/{run_id}/next-level` 进入后端真相源。
|
||||
|
||||
@@ -67,12 +67,6 @@ use crate::{
|
||||
},
|
||||
error_middleware::normalize_error_response,
|
||||
health::health_check,
|
||||
legacy_generated_assets::{
|
||||
proxy_generated_animations, proxy_generated_big_fish_assets,
|
||||
proxy_generated_character_drafts, proxy_generated_characters,
|
||||
proxy_generated_custom_world_covers, proxy_generated_custom_world_scenes,
|
||||
proxy_generated_puzzle_assets, proxy_generated_qwen_sprites,
|
||||
},
|
||||
llm::proxy_llm_chat_completions,
|
||||
login_options::auth_login_options,
|
||||
logout::logout,
|
||||
@@ -81,12 +75,11 @@ use crate::{
|
||||
password_management::{change_password, reset_password},
|
||||
phone_auth::{phone_login, send_phone_code},
|
||||
puzzle::{
|
||||
advance_local_puzzle_next_level, advance_puzzle_next_level, create_puzzle_agent_session,
|
||||
delete_puzzle_work, drag_puzzle_piece_or_group, execute_puzzle_agent_action,
|
||||
get_puzzle_agent_session, get_puzzle_gallery_detail, get_puzzle_run,
|
||||
get_puzzle_work_detail, get_puzzle_works, list_puzzle_gallery, put_puzzle_work,
|
||||
start_puzzle_run, stream_puzzle_agent_message, submit_puzzle_agent_message,
|
||||
submit_puzzle_leaderboard, swap_puzzle_pieces,
|
||||
advance_puzzle_next_level, create_puzzle_agent_session, delete_puzzle_work,
|
||||
drag_puzzle_piece_or_group, execute_puzzle_agent_action, get_puzzle_agent_session,
|
||||
get_puzzle_gallery_detail, get_puzzle_run, get_puzzle_work_detail, get_puzzle_works,
|
||||
list_puzzle_gallery, put_puzzle_work, start_puzzle_run, stream_puzzle_agent_message,
|
||||
submit_puzzle_agent_message, submit_puzzle_leaderboard, swap_puzzle_pieces,
|
||||
},
|
||||
refresh_session::refresh_session,
|
||||
request_context::{attach_request_context, resolve_request_id},
|
||||
@@ -117,7 +110,8 @@ use crate::{
|
||||
create_story_battle, create_story_npc_battle, get_story_battle_state, resolve_story_battle,
|
||||
},
|
||||
story_sessions::{
|
||||
begin_story_session, continue_story, get_story_runtime_projection, get_story_session_state,
|
||||
begin_story_runtime_session, begin_story_session, continue_story,
|
||||
get_story_runtime_projection, get_story_session_state, resolve_story_runtime_action,
|
||||
},
|
||||
wechat_auth::{bind_wechat_phone, handle_wechat_callback, start_wechat_login},
|
||||
};
|
||||
@@ -193,38 +187,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/auth/public-users/by-id/{user_id}",
|
||||
get(get_public_user_by_id),
|
||||
)
|
||||
.route(
|
||||
"/generated-character-drafts/{*path}",
|
||||
get(proxy_generated_character_drafts),
|
||||
)
|
||||
.route(
|
||||
"/generated-characters/{*path}",
|
||||
get(proxy_generated_characters),
|
||||
)
|
||||
.route(
|
||||
"/generated-animations/{*path}",
|
||||
get(proxy_generated_animations),
|
||||
)
|
||||
.route(
|
||||
"/generated-big-fish-assets/{*path}",
|
||||
get(proxy_generated_big_fish_assets),
|
||||
)
|
||||
.route(
|
||||
"/generated-puzzle-assets/{*path}",
|
||||
get(proxy_generated_puzzle_assets),
|
||||
)
|
||||
.route(
|
||||
"/generated-custom-world-scenes/{*path}",
|
||||
get(proxy_generated_custom_world_scenes),
|
||||
)
|
||||
.route(
|
||||
"/generated-custom-world-covers/{*path}",
|
||||
get(proxy_generated_custom_world_covers),
|
||||
)
|
||||
.route(
|
||||
"/generated-qwen-sprites/{*path}",
|
||||
get(proxy_generated_qwen_sprites),
|
||||
)
|
||||
.route(
|
||||
"/api/auth/me",
|
||||
get(auth_me).route_layer(middleware::from_fn_with_state(
|
||||
@@ -738,13 +700,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/puzzle/runs/local-next-level",
|
||||
post(advance_local_puzzle_next_level).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/puzzle/runs/{run_id}",
|
||||
get(get_puzzle_run).route_layer(middleware::from_fn_with_state(
|
||||
@@ -787,13 +742,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/custom-world/entity",
|
||||
post(generate_custom_world_entity).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/custom-world/entity",
|
||||
post(generate_custom_world_entity).route_layer(middleware::from_fn_with_state(
|
||||
@@ -801,13 +749,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/custom-world/scene-npc",
|
||||
post(generate_custom_world_scene_npc).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/custom-world/scene-npc",
|
||||
post(generate_custom_world_scene_npc).route_layer(middleware::from_fn_with_state(
|
||||
@@ -816,19 +757,12 @@ pub fn build_router(state: AppState) -> Router {
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/custom-world/scene-image",
|
||||
"/api/runtime/custom-world/scene-image",
|
||||
post(generate_custom_world_scene_image).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/custom-world/cover-image",
|
||||
post(generate_custom_world_cover_image).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/custom-world/cover-image",
|
||||
post(generate_custom_world_cover_image).route_layer(middleware::from_fn_with_state(
|
||||
@@ -836,13 +770,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/custom-world/cover-upload",
|
||||
post(upload_custom_world_cover_image).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/runtime/custom-world/cover-upload",
|
||||
post(upload_custom_world_cover_image).route_layer(middleware::from_fn_with_state(
|
||||
@@ -944,6 +871,13 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/story/sessions/runtime",
|
||||
post(begin_story_runtime_session).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/story/sessions/{story_session_id}/state",
|
||||
get(get_story_session_state).route_layer(middleware::from_fn_with_state(
|
||||
@@ -958,6 +892,13 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/story/sessions/{story_session_id}/actions/resolve",
|
||||
post(resolve_story_runtime_action).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/story/sessions/continue",
|
||||
post(continue_story).route_layer(middleware::from_fn_with_state(
|
||||
@@ -1263,6 +1204,85 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deleted_old_routes_are_not_mounted() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
// 中文注释:旧 custom-world 非 runtime 前缀没有任何新路由可匹配,
|
||||
// 因此必须稳定返回 404,避免前端继续误用旧入口。
|
||||
for uri in [
|
||||
"/api/custom-world/entity",
|
||||
"/api/custom-world/scene-npc",
|
||||
"/api/custom-world/scene-image",
|
||||
"/api/custom-world/cover-image",
|
||||
"/api/custom-world/cover-upload",
|
||||
] {
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri(uri)
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::empty())
|
||||
.expect("deleted old route request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("deleted old route request should be handled");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/runtime/puzzle/runs/local-next-level")
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::empty())
|
||||
.expect("deleted old puzzle route request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("deleted old puzzle route request should be handled");
|
||||
|
||||
// 中文注释:该路径会被现有 GET /runs/{run_id} 的动态段识别,
|
||||
// 但 POST 方法没有挂载,返回 405 代表旧 local-next-level handler 已移除。
|
||||
assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generated_asset_read_proxy_routes_are_not_mounted() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
// 中文注释:生成资产仍可作为 legacyPublicPath 传给 /api/assets/read-url,
|
||||
// 但不能再通过 /generated-* 同源路由裸读 OSS 对象。
|
||||
for uri in [
|
||||
"/generated-character-drafts/hero/visual/candidate.png",
|
||||
"/generated-characters/hero/visual/master.png",
|
||||
"/generated-animations/hero/idle/frame01.png",
|
||||
"/generated-big-fish-assets/session-1/level/image.png",
|
||||
"/generated-puzzle-assets/session-1/candidate/image.png",
|
||||
"/generated-custom-world-scenes/world-1/camp/scene.png",
|
||||
"/generated-custom-world-covers/world-1/cover.webp",
|
||||
"/generated-qwen-sprites/master/candidate-01.png",
|
||||
] {
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("GET")
|
||||
.uri(uri)
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::empty())
|
||||
.expect("generated asset proxy route request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("generated asset proxy route request should be handled");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn internal_auth_claims_rejects_missing_bearer_token() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
@@ -442,6 +442,8 @@ pub async fn generate_custom_world_scene_image(
|
||||
let owner_user_id = authenticated.claims().user_id().to_string();
|
||||
let normalized = normalize_scene_image_request(payload)
|
||||
.map_err(|error| custom_world_ai_error_response(&request_context, error))?;
|
||||
require_dashscope_settings(&state)
|
||||
.map_err(|error| custom_world_ai_error_response(&request_context, error))?;
|
||||
let asset_id = format!("custom-scene-{}", current_utc_millis());
|
||||
let asset = execute_billable_asset_operation(
|
||||
&state,
|
||||
@@ -703,6 +705,8 @@ pub async fn generate_custom_world_cover_image(
|
||||
trim_to_option(payload.profile.name.as_deref()).unwrap_or_else(|| "world".to_string());
|
||||
let entity_id = profile_id.clone().unwrap_or_else(|| world_name.clone());
|
||||
let size = trim_to_option(payload.size.as_deref()).unwrap_or_else(|| "1600*900".to_string());
|
||||
require_dashscope_settings(&state)
|
||||
.map_err(|error| custom_world_ai_error_response(&request_context, error))?;
|
||||
let asset_id = format!("custom-cover-{}", current_utc_millis());
|
||||
let asset = execute_billable_asset_operation(
|
||||
&state,
|
||||
@@ -2440,10 +2444,16 @@ mod tests {
|
||||
serde_json::from_slice(&body).expect("body should be valid json")
|
||||
}
|
||||
|
||||
fn build_state_without_dashscope_key() -> AppState {
|
||||
let mut config = AppConfig::default();
|
||||
config.dashscope_api_key = None;
|
||||
AppState::new(config).expect("state should build")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn scene_image_returns_service_unavailable_when_dashscope_missing() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let request_context = build_request_context("POST /api/custom-world/scene-image");
|
||||
let state = build_state_without_dashscope_key();
|
||||
let request_context = build_request_context("POST /api/runtime/custom-world/scene-image");
|
||||
let authenticated = build_authenticated(&state);
|
||||
|
||||
let response = generate_custom_world_scene_image(
|
||||
@@ -2512,15 +2522,27 @@ mod tests {
|
||||
};
|
||||
|
||||
let normalized = normalize_scene_image_request(payload).expect("payload should normalize");
|
||||
let manual_prompt = build_custom_world_scene_image_prompt(SceneImagePromptParams {
|
||||
profile: SceneImagePromptProfile {
|
||||
name: "雾海群岛",
|
||||
subtitle: "失落航线",
|
||||
tone: "潮湿、神秘、低魔奇幻",
|
||||
player_goal: "找到王冠并阻止海妖复苏",
|
||||
summary: "玩家在雾海中追查沉没王冠。",
|
||||
setting_text: "群岛被永恒雾潮包围。",
|
||||
},
|
||||
landmark: SceneImagePromptLandmark {
|
||||
name: "礁石神殿",
|
||||
description: "古老礁石上的半沉神殿。",
|
||||
},
|
||||
user_prompt: "破碎神殿矗立在蓝绿色雾潮中,潮湿石阶上有幽光贝壳。",
|
||||
has_reference_image: false,
|
||||
fallback_landmark_name: Some("礁石神殿"),
|
||||
fallback_world_name: "雾海群岛",
|
||||
});
|
||||
|
||||
assert!(normalized.prompt.contains("世界名:雾海群岛"));
|
||||
assert!(normalized.prompt.contains("世界副标题:失落航线"));
|
||||
assert!(normalized.prompt.contains("场景名称:礁石神殿"));
|
||||
assert!(
|
||||
normalized
|
||||
.prompt
|
||||
.contains("本次想要生成的画面内容:破碎神殿")
|
||||
);
|
||||
assert_eq!(normalized.prompt, manual_prompt);
|
||||
assert!(normalized.prompt.contains("破碎神殿矗立在蓝绿色雾潮中"));
|
||||
assert_ne!(
|
||||
normalized.prompt,
|
||||
"破碎神殿矗立在蓝绿色雾潮中,潮湿石阶上有幽光贝壳。"
|
||||
@@ -2585,8 +2607,8 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn cover_image_returns_service_unavailable_when_dashscope_missing() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let request_context = build_request_context("POST /api/custom-world/cover-image");
|
||||
let state = build_state_without_dashscope_key();
|
||||
let request_context = build_request_context("POST /api/runtime/custom-world/cover-image");
|
||||
let authenticated = build_authenticated(&state);
|
||||
|
||||
let response = generate_custom_world_cover_image(
|
||||
@@ -2622,7 +2644,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn cover_upload_rejects_invalid_data_url_before_touching_oss() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let request_context = build_request_context("POST /api/custom-world/cover-upload");
|
||||
let request_context = build_request_context("POST /api/runtime/custom-world/cover-upload");
|
||||
let authenticated = build_authenticated(&state);
|
||||
|
||||
let response = upload_custom_world_cover_image(
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::{HeaderMap, HeaderName, HeaderValue, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use platform_oss::{LegacyAssetPrefix, OssSignedGetObjectUrlRequest};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{http_error::AppError, platform_errors::map_oss_error, state::AppState};
|
||||
|
||||
const CACHE_CONTROL_VALUE: &str = "private, max-age=60";
|
||||
const ASSET_OBJECT_KEY_HEADER: &str = "x-genarrative-asset-object-key";
|
||||
|
||||
pub async fn proxy_generated_character_drafts(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::CharacterDrafts, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_characters(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::Characters, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_animations(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::Animations, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_big_fish_assets(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::BigFishAssets, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_puzzle_assets(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::PuzzleAssets, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_custom_world_scenes(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::CustomWorldScenes, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_custom_world_covers(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::CustomWorldCovers, path).await
|
||||
}
|
||||
|
||||
pub async fn proxy_generated_qwen_sprites(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> Response {
|
||||
proxy_legacy_generated_asset(state, LegacyAssetPrefix::QwenSprites, path).await
|
||||
}
|
||||
|
||||
async fn proxy_legacy_generated_asset(
|
||||
state: AppState,
|
||||
prefix: LegacyAssetPrefix,
|
||||
path: String,
|
||||
) -> Response {
|
||||
match read_legacy_generated_asset(&state, prefix, path).await {
|
||||
Ok(response) => response,
|
||||
Err(error) => error.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_legacy_generated_asset(
|
||||
state: &AppState,
|
||||
prefix: LegacyAssetPrefix,
|
||||
path: String,
|
||||
) -> Result<Response, AppError> {
|
||||
let oss_client = state.oss_client().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({
|
||||
"provider": "aliyun-oss",
|
||||
"reason": "OSS 未完成环境变量配置",
|
||||
}))
|
||||
})?;
|
||||
let object_key = build_generated_object_key(prefix, path.as_str())?;
|
||||
let signed = oss_client
|
||||
.sign_get_object_url(OssSignedGetObjectUrlRequest {
|
||||
object_key: object_key.clone(),
|
||||
expire_seconds: Some(60),
|
||||
})
|
||||
.map_err(map_legacy_generated_oss_error)?;
|
||||
let upstream_response = reqwest::Client::new()
|
||||
.get(signed.signed_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "aliyun-oss",
|
||||
"message": format!("读取 OSS 旧 generated 资源失败:{error}"),
|
||||
}))
|
||||
})?;
|
||||
|
||||
if upstream_response.status() == reqwest::StatusCode::NOT_FOUND {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::NOT_FOUND).with_details(json!({
|
||||
"provider": "aliyun-oss",
|
||||
"objectKey": object_key,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
let status = upstream_response.status();
|
||||
let content_type = upstream_response
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
.cloned();
|
||||
if !status.is_success() {
|
||||
return Err(map_legacy_generated_upstream_status(status, object_key));
|
||||
}
|
||||
|
||||
let bytes = upstream_response.bytes().await.map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "aliyun-oss",
|
||||
"message": format!("读取 OSS 旧 generated 资源内容失败:{error}"),
|
||||
}))
|
||||
})?;
|
||||
|
||||
// 旧 generated 路径会被 <img> / <video> 直接消费,成功分支必须返回原始二进制体。
|
||||
// 这里显式组装 HeaderMap 并设置长度,避免代理层把已成功读取的 OSS 对象变成空响应。
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CACHE_CONTROL,
|
||||
HeaderValue::from_static(CACHE_CONTROL_VALUE),
|
||||
);
|
||||
headers.insert(
|
||||
HeaderName::from_static(ASSET_OBJECT_KEY_HEADER),
|
||||
HeaderValue::from_str(object_key.as_str()).map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"provider": "legacy-generated-assets",
|
||||
"message": format!("构造资源响应头失败:{error}"),
|
||||
}))
|
||||
})?,
|
||||
);
|
||||
headers.insert(
|
||||
header::CONTENT_LENGTH,
|
||||
HeaderValue::from_str(bytes.len().to_string().as_str()).map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"provider": "legacy-generated-assets",
|
||||
"message": format!("构造资源长度响应头失败:{error}"),
|
||||
}))
|
||||
})?,
|
||||
);
|
||||
if let Some(content_type) = content_type {
|
||||
headers.insert(header::CONTENT_TYPE, content_type);
|
||||
}
|
||||
|
||||
Ok((status, headers, bytes).into_response())
|
||||
}
|
||||
|
||||
fn build_generated_object_key(prefix: LegacyAssetPrefix, path: &str) -> Result<String, AppError> {
|
||||
let path = path.trim().trim_matches('/');
|
||||
if path.is_empty() || path.split('/').any(is_invalid_path_segment) {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "legacy-generated-assets",
|
||||
"message": "generated 资源路径不合法。",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(format!("{}/{}", prefix.as_str(), path))
|
||||
}
|
||||
|
||||
fn is_invalid_path_segment(segment: &str) -> bool {
|
||||
segment.is_empty() || segment == "." || segment == ".." || segment.contains('\\')
|
||||
}
|
||||
|
||||
fn map_legacy_generated_oss_error(error: platform_oss::OssError) -> AppError {
|
||||
map_oss_error(error, "aliyun-oss")
|
||||
}
|
||||
|
||||
fn map_legacy_generated_upstream_status(
|
||||
status: reqwest::StatusCode,
|
||||
object_key: String,
|
||||
) -> AppError {
|
||||
let mapped_status = match status {
|
||||
reqwest::StatusCode::NOT_FOUND => StatusCode::NOT_FOUND,
|
||||
reqwest::StatusCode::FORBIDDEN | reqwest::StatusCode::UNAUTHORIZED => {
|
||||
StatusCode::BAD_GATEWAY
|
||||
}
|
||||
_ => StatusCode::BAD_GATEWAY,
|
||||
};
|
||||
|
||||
AppError::from_status(mapped_status).with_details(json!({
|
||||
"provider": "aliyun-oss",
|
||||
"objectKey": object_key,
|
||||
"upstreamStatus": status.as_u16(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn build_generated_object_key_keeps_supported_prefix() {
|
||||
let object_key = build_generated_object_key(
|
||||
LegacyAssetPrefix::Animations,
|
||||
"hero/animation-set-1/idle/frame01.png",
|
||||
)
|
||||
.expect("object key should build");
|
||||
|
||||
assert_eq!(
|
||||
object_key,
|
||||
"generated-animations/hero/animation-set-1/idle/frame01.png"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_generated_object_key_supports_big_fish_assets() {
|
||||
let object_key = build_generated_object_key(
|
||||
LegacyAssetPrefix::BigFishAssets,
|
||||
"big-fish-session-1/level-main-image/level-1/image.png",
|
||||
)
|
||||
.expect("object key should build");
|
||||
|
||||
assert_eq!(
|
||||
object_key,
|
||||
"generated-big-fish-assets/big-fish-session-1/level-main-image/level-1/image.png"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_generated_object_key_rejects_parent_segment() {
|
||||
assert!(
|
||||
build_generated_object_key(LegacyAssetPrefix::Characters, "../secret.png").is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,17 @@ use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
response::{
|
||||
IntoResponse, Response,
|
||||
sse::{Event, Sse},
|
||||
},
|
||||
};
|
||||
use platform_llm::{LlmMessage, LlmMessageRole, LlmTextRequest};
|
||||
use serde_json::Value;
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::llm::{
|
||||
LlmChatCompletionRequest, LlmChatCompletionResponse, LlmChatMessagePayload, LlmChatMessageRole,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
@@ -20,15 +24,7 @@ pub async fn proxy_llm_chat_completions(
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(_authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<LlmChatCompletionRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
if payload.stream {
|
||||
return Err(llm_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::NOT_IMPLEMENTED)
|
||||
.with_message("Rust `api-server` 首版暂不支持流式 LLM 代理"),
|
||||
));
|
||||
}
|
||||
|
||||
) -> Result<Response, Response> {
|
||||
let llm_client = state.llm_client().ok_or_else(|| {
|
||||
llm_error_response(
|
||||
&request_context,
|
||||
@@ -48,6 +44,10 @@ pub async fn proxy_llm_chat_completions(
|
||||
enable_web_search: false,
|
||||
};
|
||||
|
||||
if payload.stream {
|
||||
return Ok(stream_llm_chat_completions(llm_client.clone(), request).into_response());
|
||||
}
|
||||
|
||||
let response = llm_client
|
||||
.request_text(request)
|
||||
.await
|
||||
@@ -61,7 +61,78 @@ pub async fn proxy_llm_chat_completions(
|
||||
content: response.content,
|
||||
finish_reason: response.finish_reason,
|
||||
},
|
||||
))
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
fn stream_llm_chat_completions(
|
||||
llm_client: platform_llm::LlmClient,
|
||||
request: LlmTextRequest,
|
||||
) -> Sse<impl tokio_stream::Stream<Item = Result<Event, Infallible>>> {
|
||||
let stream = async_stream::stream! {
|
||||
let (delta_tx, mut delta_rx) = tokio::sync::mpsc::unbounded_channel::<Value>();
|
||||
let llm_stream = llm_client.stream_text(request, move |delta| {
|
||||
let _ = delta_tx.send(json!({
|
||||
"delta": delta.delta_text,
|
||||
"content": delta.accumulated_text,
|
||||
"finishReason": delta.finish_reason,
|
||||
}));
|
||||
});
|
||||
tokio::pin!(llm_stream);
|
||||
|
||||
let llm_result = loop {
|
||||
// `platform-llm` 负责上游 SSE 解析;这里尽快把增量转成 API 层 SSE 事件。
|
||||
tokio::select! {
|
||||
result = &mut llm_stream => break result,
|
||||
maybe_delta = delta_rx.recv() => {
|
||||
if let Some(delta) = maybe_delta {
|
||||
yield Ok::<Event, Infallible>(llm_sse_json_event_or_error("delta", delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(delta) = delta_rx.recv().await {
|
||||
yield Ok::<Event, Infallible>(llm_sse_json_event_or_error("delta", delta));
|
||||
}
|
||||
|
||||
match llm_result {
|
||||
Ok(response) => {
|
||||
yield Ok::<Event, Infallible>(llm_sse_json_event_or_error(
|
||||
"complete",
|
||||
json!(LlmChatCompletionResponse {
|
||||
id: response.response_id,
|
||||
model: response.model,
|
||||
content: response.content,
|
||||
finish_reason: response.finish_reason,
|
||||
}),
|
||||
));
|
||||
}
|
||||
Err(error) => {
|
||||
let app_error = map_llm_error(error);
|
||||
yield Ok::<Event, Infallible>(llm_sse_json_event_or_error(
|
||||
"error",
|
||||
json!({
|
||||
"code": app_error.code(),
|
||||
"message": app_error.message(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
yield Ok::<Event, Infallible>(Event::default().data("[DONE]"));
|
||||
};
|
||||
|
||||
Sse::new(stream)
|
||||
}
|
||||
|
||||
fn llm_sse_json_event_or_error(event_name: &str, payload: Value) -> Event {
|
||||
match serde_json::to_string(&payload) {
|
||||
Ok(payload_text) => Event::default().event(event_name).data(payload_text),
|
||||
Err(_) => Event::default()
|
||||
.event("error")
|
||||
.data("{\"code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"SSE payload 序列化失败\"}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_chat_message(message: LlmChatMessagePayload) -> LlmMessage {
|
||||
@@ -105,6 +176,7 @@ mod tests {
|
||||
status_line: &'static str,
|
||||
content_type: &'static str,
|
||||
body: String,
|
||||
extra_headers: Vec<(&'static str, &'static str)>,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -113,6 +185,7 @@ mod tests {
|
||||
status_line: "200 OK",
|
||||
content_type: "application/json; charset=utf-8",
|
||||
body: r#"{"id":"resp_api_server_01","model":"ark-router-test","choices":[{"message":{"content":"代理成功"},"finish_reason":"stop"}]}"#.to_string(),
|
||||
extra_headers: Vec::new(),
|
||||
}]);
|
||||
let state = seed_authenticated_state(AppConfig {
|
||||
llm_base_url: server_url,
|
||||
@@ -176,8 +249,25 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn llm_chat_completions_rejects_stream_mode() {
|
||||
let state = seed_authenticated_state(AppConfig::default()).await;
|
||||
async fn llm_chat_completions_streams_sse_payload() {
|
||||
let server_url = spawn_mock_server(vec![MockResponse {
|
||||
status_line: "200 OK",
|
||||
content_type: "text/event-stream; charset=utf-8",
|
||||
body: concat!(
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"你\"}}]}\n\n",
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"好\"}}]}\n\n",
|
||||
"data: {\"choices\":[{\"finish_reason\":\"stop\"}]}\n\n",
|
||||
"data: [DONE]\n\n"
|
||||
)
|
||||
.to_string(),
|
||||
extra_headers: vec![("x-request-id", "req_llm_stream_01")],
|
||||
}]);
|
||||
let state = seed_authenticated_state(AppConfig {
|
||||
llm_base_url: server_url,
|
||||
llm_api_key: Some("test-key".to_string()),
|
||||
..AppConfig::default()
|
||||
})
|
||||
.await;
|
||||
let token = issue_access_token(&state);
|
||||
let app = build_router(state);
|
||||
|
||||
@@ -188,7 +278,6 @@ mod tests {
|
||||
.uri("/api/llm/chat/completions")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.header("content-type", "application/json")
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"stream": true,
|
||||
@@ -203,7 +292,14 @@ mod tests {
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::NOT_IMPLEMENTED);
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("text/event-stream")
|
||||
);
|
||||
|
||||
let body = response
|
||||
.into_body()
|
||||
@@ -211,14 +307,15 @@ mod tests {
|
||||
.await
|
||||
.expect("body should collect")
|
||||
.to_bytes();
|
||||
let payload: Value =
|
||||
serde_json::from_slice(&body).expect("response body should be valid json");
|
||||
let body_text = String::from_utf8(body.to_vec()).expect("body should be utf8");
|
||||
|
||||
assert_eq!(payload["ok"], Value::Bool(false));
|
||||
assert_eq!(
|
||||
payload["error"]["code"],
|
||||
Value::String("NOT_IMPLEMENTED".to_string())
|
||||
);
|
||||
assert!(body_text.contains("event: delta"));
|
||||
assert!(body_text.contains(r#""delta":"你""#));
|
||||
assert!(body_text.contains(r#""content":"你好""#));
|
||||
assert!(body_text.contains("event: complete"));
|
||||
assert!(body_text.contains(r#""id":"req_llm_stream_01""#));
|
||||
assert!(body_text.contains(r#""finishReason":"stop""#));
|
||||
assert!(body_text.contains("data: [DONE]"));
|
||||
}
|
||||
|
||||
async fn seed_authenticated_state(config: AppConfig) -> AppState {
|
||||
@@ -306,13 +403,17 @@ mod tests {
|
||||
|
||||
fn write_response(stream: &mut std::net::TcpStream, response: MockResponse) {
|
||||
let body = response.body;
|
||||
let raw_response = format!(
|
||||
"HTTP/1.1 {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||
let mut raw_response = format!(
|
||||
"HTTP/1.1 {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n",
|
||||
response.status_line,
|
||||
response.content_type,
|
||||
body.len(),
|
||||
body
|
||||
body.len()
|
||||
);
|
||||
for (name, value) in response.extra_headers {
|
||||
raw_response.push_str(format!("{name}: {value}\r\n").as_str());
|
||||
}
|
||||
raw_response.push_str("\r\n");
|
||||
raw_response.push_str(body.as_str());
|
||||
|
||||
stream
|
||||
.write_all(raw_response.as_bytes())
|
||||
|
||||
@@ -34,7 +34,6 @@ mod custom_world_rpg_draft_prompts;
|
||||
mod error_middleware;
|
||||
mod health;
|
||||
mod http_error;
|
||||
mod legacy_generated_assets;
|
||||
mod llm;
|
||||
mod login_options;
|
||||
mod logout;
|
||||
|
||||
@@ -17,7 +17,7 @@ use module_assets::{
|
||||
AssetObjectAccessPolicy, AssetObjectFieldError, build_asset_entity_binding_input,
|
||||
build_asset_object_upsert_input, generate_asset_binding_id, generate_asset_object_id,
|
||||
};
|
||||
use module_puzzle::{PuzzleBoardSnapshot, PuzzleGeneratedImageCandidate};
|
||||
use module_puzzle::PuzzleGeneratedImageCandidate;
|
||||
use platform_oss::{
|
||||
LegacyAssetPrefix, OssHeadObjectRequest, OssObjectAccess, OssPutObjectRequest,
|
||||
OssSignedGetObjectUrlRequest,
|
||||
@@ -36,11 +36,10 @@ use shared_contracts::{
|
||||
},
|
||||
puzzle_gallery::{PuzzleGalleryDetailResponse, PuzzleGalleryResponse},
|
||||
puzzle_runtime::{
|
||||
AdvanceLocalPuzzleNextLevelRequest, DragPuzzlePieceRequest, PuzzleBoardSnapshotResponse,
|
||||
PuzzleCellPositionResponse, PuzzleLeaderboardEntryResponse, PuzzleMergedGroupStateResponse,
|
||||
PuzzlePieceStateResponse, PuzzleRunResponse, PuzzleRunSnapshotResponse,
|
||||
PuzzleRuntimeLevelSnapshotResponse, StartPuzzleRunRequest, SubmitPuzzleLeaderboardRequest,
|
||||
SwapPuzzlePiecesRequest,
|
||||
DragPuzzlePieceRequest, PuzzleBoardSnapshotResponse, PuzzleCellPositionResponse,
|
||||
PuzzleLeaderboardEntryResponse, PuzzleMergedGroupStateResponse, PuzzlePieceStateResponse,
|
||||
PuzzleRunResponse, PuzzleRunSnapshotResponse, PuzzleRuntimeLevelSnapshotResponse,
|
||||
StartPuzzleRunRequest, SubmitPuzzleLeaderboardRequest, SwapPuzzlePiecesRequest,
|
||||
},
|
||||
puzzle_works::{
|
||||
PutPuzzleWorkRequest, PuzzleWorkDetailResponse, PuzzleWorkMutationResponse,
|
||||
@@ -52,14 +51,13 @@ use spacetime_client::{
|
||||
PuzzleAgentMessageRecord, PuzzleAgentMessageSubmitRecordInput,
|
||||
PuzzleAgentSessionCreateRecordInput, PuzzleAgentSessionRecord,
|
||||
PuzzleAgentSuggestedActionRecord, PuzzleAnchorItemRecord, PuzzleAnchorPackRecord,
|
||||
PuzzleBoardRecord, PuzzleCellPositionRecord, PuzzleCreatorIntentRecord,
|
||||
PuzzleGeneratedImageCandidateRecord, PuzzleGeneratedImagesSaveRecordInput,
|
||||
PuzzleLeaderboardEntryRecord, PuzzleLeaderboardSubmitRecordInput, PuzzleMergedGroupRecord,
|
||||
PuzzlePieceStateRecord, PuzzlePublishRecordInput, PuzzleResultDraftRecord,
|
||||
PuzzleCreatorIntentRecord, PuzzleGeneratedImageCandidateRecord,
|
||||
PuzzleGeneratedImagesSaveRecordInput, PuzzleLeaderboardEntryRecord,
|
||||
PuzzleLeaderboardSubmitRecordInput, PuzzlePublishRecordInput, PuzzleResultDraftRecord,
|
||||
PuzzleResultPreviewBlockerRecord, PuzzleResultPreviewFindingRecord, PuzzleResultPreviewRecord,
|
||||
PuzzleRunDragRecordInput, PuzzleRunRecord, PuzzleRunStartRecordInput, PuzzleRunSwapRecordInput,
|
||||
PuzzleRuntimeLevelRecord, PuzzleSelectCoverImageRecordInput, PuzzleWorkProfileRecord,
|
||||
PuzzleWorkUpsertRecordInput, SpacetimeClientError,
|
||||
PuzzleSelectCoverImageRecordInput, PuzzleWorkProfileRecord, PuzzleWorkUpsertRecordInput,
|
||||
SpacetimeClientError,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
use tokio::time::sleep;
|
||||
@@ -1100,36 +1098,6 @@ pub async fn advance_puzzle_next_level(
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn advance_local_puzzle_next_level(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
payload: Result<Json<AdvanceLocalPuzzleNextLevelRequest>, JsonRejection>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let Json(payload) = payload.map_err(|error| {
|
||||
puzzle_error_response(
|
||||
&request_context,
|
||||
PUZZLE_RUNTIME_PROVIDER,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": error.body_text(),
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
|
||||
let owner_user_id = authenticated.claims().user_id().to_string();
|
||||
let run = build_local_next_puzzle_run(&state, payload, owner_user_id.as_str())
|
||||
.await
|
||||
.map_err(|error| puzzle_error_response(&request_context, PUZZLE_RUNTIME_PROVIDER, error))?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
PuzzleRunResponse {
|
||||
run: map_puzzle_run_response(run),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn submit_puzzle_leaderboard(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(run_id): AxumPath<String>,
|
||||
@@ -1385,98 +1353,6 @@ fn map_puzzle_run_response(run: PuzzleRunRecord) -> PuzzleRunSnapshotResponse {
|
||||
}
|
||||
}
|
||||
|
||||
fn map_puzzle_run_request_record(run: PuzzleRunSnapshotResponse) -> PuzzleRunRecord {
|
||||
PuzzleRunRecord {
|
||||
run_id: run.run_id,
|
||||
entry_profile_id: run.entry_profile_id,
|
||||
cleared_level_count: run.cleared_level_count,
|
||||
current_level_index: run.current_level_index,
|
||||
current_grid_size: run.current_grid_size,
|
||||
played_profile_ids: run.played_profile_ids,
|
||||
previous_level_tags: run.previous_level_tags,
|
||||
current_level: run.current_level.map(map_puzzle_level_request_record),
|
||||
recommended_next_profile_id: run.recommended_next_profile_id,
|
||||
leaderboard_entries: run
|
||||
.leaderboard_entries
|
||||
.into_iter()
|
||||
.map(map_puzzle_leaderboard_request_record)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_puzzle_level_request_record(
|
||||
level: PuzzleRuntimeLevelSnapshotResponse,
|
||||
) -> PuzzleRuntimeLevelRecord {
|
||||
PuzzleRuntimeLevelRecord {
|
||||
run_id: level.run_id,
|
||||
level_index: level.level_index,
|
||||
grid_size: level.grid_size,
|
||||
profile_id: level.profile_id,
|
||||
level_name: level.level_name,
|
||||
author_display_name: level.author_display_name,
|
||||
theme_tags: level.theme_tags,
|
||||
cover_image_src: level.cover_image_src,
|
||||
board: map_puzzle_board_request_record(level.board),
|
||||
status: level.status,
|
||||
started_at_ms: level.started_at_ms,
|
||||
cleared_at_ms: level.cleared_at_ms,
|
||||
elapsed_ms: level.elapsed_ms,
|
||||
leaderboard_entries: level
|
||||
.leaderboard_entries
|
||||
.into_iter()
|
||||
.map(map_puzzle_leaderboard_request_record)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_puzzle_leaderboard_request_record(
|
||||
entry: PuzzleLeaderboardEntryResponse,
|
||||
) -> PuzzleLeaderboardEntryRecord {
|
||||
PuzzleLeaderboardEntryRecord {
|
||||
rank: entry.rank,
|
||||
nickname: entry.nickname,
|
||||
elapsed_ms: entry.elapsed_ms,
|
||||
is_current_player: entry.is_current_player,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_puzzle_board_request_record(board: PuzzleBoardSnapshotResponse) -> PuzzleBoardRecord {
|
||||
PuzzleBoardRecord {
|
||||
rows: board.rows,
|
||||
cols: board.cols,
|
||||
pieces: board
|
||||
.pieces
|
||||
.into_iter()
|
||||
.map(|piece| PuzzlePieceStateRecord {
|
||||
piece_id: piece.piece_id,
|
||||
correct_row: piece.correct_row,
|
||||
correct_col: piece.correct_col,
|
||||
current_row: piece.current_row,
|
||||
current_col: piece.current_col,
|
||||
merged_group_id: piece.merged_group_id,
|
||||
})
|
||||
.collect(),
|
||||
merged_groups: board
|
||||
.merged_groups
|
||||
.into_iter()
|
||||
.map(|group| PuzzleMergedGroupRecord {
|
||||
group_id: group.group_id,
|
||||
piece_ids: group.piece_ids,
|
||||
occupied_cells: group
|
||||
.occupied_cells
|
||||
.into_iter()
|
||||
.map(|cell| PuzzleCellPositionRecord {
|
||||
row: cell.row,
|
||||
col: cell.col,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
selected_piece_id: board.selected_piece_id,
|
||||
all_tiles_resolved: board.all_tiles_resolved,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_puzzle_runtime_level_response(
|
||||
level: spacetime_client::PuzzleRuntimeLevelRecord,
|
||||
) -> PuzzleRuntimeLevelSnapshotResponse {
|
||||
@@ -1842,343 +1718,6 @@ async fn generate_puzzle_image_candidates(
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn build_local_next_puzzle_run(
|
||||
state: &AppState,
|
||||
payload: AdvanceLocalPuzzleNextLevelRequest,
|
||||
owner_user_id: &str,
|
||||
) -> Result<PuzzleRunRecord, AppError> {
|
||||
let run = map_puzzle_run_request_record(payload.run);
|
||||
let current_level = run.current_level.clone().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "currentLevel is required",
|
||||
}))
|
||||
})?;
|
||||
if current_level.status != "cleared" {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "current level is not cleared",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(gallery_item) = resolve_gallery_next_puzzle_work(state, &run).await? {
|
||||
return Ok(build_next_run_from_puzzle_work(run, gallery_item));
|
||||
}
|
||||
|
||||
let source_session_id = payload.source_session_id.unwrap_or_default();
|
||||
if source_session_id.trim().is_empty() {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "sourceSessionId is required when gallery has no next puzzle work",
|
||||
})),
|
||||
);
|
||||
}
|
||||
let session = state
|
||||
.spacetime_client()
|
||||
.get_puzzle_agent_session(source_session_id, owner_user_id.to_string())
|
||||
.await
|
||||
.map_err(map_puzzle_client_error)?;
|
||||
if let Some(candidate) = session
|
||||
.draft
|
||||
.as_ref()
|
||||
.and_then(|draft| pick_unused_puzzle_candidate(&draft.candidates, &run.played_profile_ids))
|
||||
{
|
||||
return Ok(build_next_run_from_candidate(run, &session, candidate));
|
||||
}
|
||||
|
||||
let draft = session.draft.clone().ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "puzzle draft is required when gallery has no next puzzle work",
|
||||
}))
|
||||
})?;
|
||||
let candidates = generate_puzzle_image_candidates(
|
||||
state,
|
||||
owner_user_id,
|
||||
&session.session_id,
|
||||
&draft.level_name,
|
||||
&draft.summary,
|
||||
None,
|
||||
1,
|
||||
draft.candidates.len(),
|
||||
)
|
||||
.await
|
||||
.map_err(|message| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": message,
|
||||
}))
|
||||
})?;
|
||||
let candidates_json = serde_json::to_string(
|
||||
&candidates
|
||||
.iter()
|
||||
.map(to_puzzle_generated_image_candidate)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": format!("拼图候选图序列化失败:{error}"),
|
||||
}))
|
||||
})?;
|
||||
let updated_session = state
|
||||
.spacetime_client()
|
||||
.save_puzzle_generated_images(PuzzleGeneratedImagesSaveRecordInput {
|
||||
session_id: session.session_id,
|
||||
owner_user_id: owner_user_id.to_string(),
|
||||
candidates_json,
|
||||
saved_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await
|
||||
.map_err(map_puzzle_client_error)?;
|
||||
let candidate = updated_session
|
||||
.draft
|
||||
.as_ref()
|
||||
.and_then(|draft| {
|
||||
draft
|
||||
.candidates
|
||||
.iter()
|
||||
.find(|candidate| !candidate.image_src.is_empty())
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": PUZZLE_RUNTIME_PROVIDER,
|
||||
"message": "现场生成后没有可用候选图",
|
||||
}))
|
||||
})?;
|
||||
Ok(build_next_run_from_candidate(
|
||||
run,
|
||||
&updated_session,
|
||||
candidate,
|
||||
))
|
||||
}
|
||||
|
||||
async fn resolve_gallery_next_puzzle_work(
|
||||
state: &AppState,
|
||||
run: &PuzzleRunRecord,
|
||||
) -> Result<Option<PuzzleWorkProfileRecord>, AppError> {
|
||||
let items = state
|
||||
.spacetime_client()
|
||||
.list_puzzle_gallery()
|
||||
.await
|
||||
.map_err(map_puzzle_client_error)?;
|
||||
Ok(items.into_iter().find(|item| {
|
||||
item.publication_status == "published"
|
||||
&& item
|
||||
.cover_image_src
|
||||
.as_ref()
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
&& !run.played_profile_ids.contains(&item.profile_id)
|
||||
}))
|
||||
}
|
||||
|
||||
fn pick_unused_puzzle_candidate<'a>(
|
||||
candidates: &'a [PuzzleGeneratedImageCandidateRecord],
|
||||
played_profile_ids: &[String],
|
||||
) -> Option<&'a PuzzleGeneratedImageCandidateRecord> {
|
||||
candidates.iter().find(|candidate| {
|
||||
!candidate.image_src.is_empty()
|
||||
&& !played_profile_ids
|
||||
.iter()
|
||||
.any(|profile_id| profile_id.contains(&candidate.candidate_id))
|
||||
})
|
||||
}
|
||||
|
||||
fn build_next_run_from_puzzle_work(
|
||||
run: PuzzleRunRecord,
|
||||
item: PuzzleWorkProfileRecord,
|
||||
) -> PuzzleRunRecord {
|
||||
build_next_run_from_parts(
|
||||
run,
|
||||
item.profile_id,
|
||||
item.level_name,
|
||||
item.author_display_name,
|
||||
item.theme_tags,
|
||||
item.cover_image_src,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_next_run_from_candidate(
|
||||
run: PuzzleRunRecord,
|
||||
session: &PuzzleAgentSessionRecord,
|
||||
candidate: &PuzzleGeneratedImageCandidateRecord,
|
||||
) -> PuzzleRunRecord {
|
||||
let draft = session.draft.as_ref();
|
||||
let level_index = run.current_level_index + 1;
|
||||
build_next_run_from_parts(
|
||||
run,
|
||||
format!(
|
||||
"{}-{}-level-{}",
|
||||
session.session_id, candidate.candidate_id, level_index
|
||||
),
|
||||
draft
|
||||
.map(|draft| format!("{} · 候选 {}", draft.level_name, level_index))
|
||||
.unwrap_or_else(|| format!("候选拼图 {level_index}")),
|
||||
"当前草稿".to_string(),
|
||||
draft
|
||||
.map(|draft| draft.theme_tags.clone())
|
||||
.unwrap_or_default(),
|
||||
Some(candidate.image_src.clone()),
|
||||
)
|
||||
}
|
||||
|
||||
fn build_next_run_from_parts(
|
||||
run: PuzzleRunRecord,
|
||||
profile_id: String,
|
||||
level_name: String,
|
||||
author_display_name: String,
|
||||
theme_tags: Vec<String>,
|
||||
cover_image_src: Option<String>,
|
||||
) -> PuzzleRunRecord {
|
||||
let next_level_index = run.current_level_index + 1;
|
||||
let grid_size = if run.cleared_level_count >= 3 { 4 } else { 3 };
|
||||
let mut played_profile_ids = run.played_profile_ids.clone();
|
||||
if !played_profile_ids.contains(&profile_id) {
|
||||
played_profile_ids.push(profile_id.clone());
|
||||
}
|
||||
let board = build_local_puzzle_board(grid_size, &run.run_id, &profile_id, next_level_index);
|
||||
PuzzleRunRecord {
|
||||
run_id: run.run_id.clone(),
|
||||
entry_profile_id: run.entry_profile_id,
|
||||
cleared_level_count: run.cleared_level_count,
|
||||
current_level_index: next_level_index,
|
||||
current_grid_size: grid_size,
|
||||
played_profile_ids,
|
||||
previous_level_tags: theme_tags.clone(),
|
||||
current_level: Some(PuzzleRuntimeLevelRecord {
|
||||
run_id: run.run_id,
|
||||
level_index: next_level_index,
|
||||
grid_size,
|
||||
profile_id,
|
||||
level_name,
|
||||
author_display_name,
|
||||
theme_tags,
|
||||
cover_image_src,
|
||||
board,
|
||||
status: "playing".to_string(),
|
||||
started_at_ms: (current_utc_micros().max(0) as u64) / 1_000,
|
||||
cleared_at_ms: None,
|
||||
elapsed_ms: None,
|
||||
leaderboard_entries: Vec::new(),
|
||||
}),
|
||||
recommended_next_profile_id: None,
|
||||
leaderboard_entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_local_puzzle_board(
|
||||
grid_size: u32,
|
||||
run_id: &str,
|
||||
profile_id: &str,
|
||||
level_index: u32,
|
||||
) -> PuzzleBoardRecord {
|
||||
let board = module_puzzle::build_initial_board_with_seed(
|
||||
grid_size,
|
||||
build_local_puzzle_shuffle_seed(run_id, profile_id, level_index, grid_size),
|
||||
)
|
||||
.unwrap_or_else(|_| {
|
||||
module_puzzle::build_initial_board_with_seed(3, 1)
|
||||
.expect("fallback puzzle board should use supported grid size")
|
||||
});
|
||||
map_puzzle_board_snapshot_record(board)
|
||||
}
|
||||
|
||||
fn build_local_puzzle_shuffle_seed(
|
||||
run_id: &str,
|
||||
profile_id: &str,
|
||||
level_index: u32,
|
||||
grid_size: u32,
|
||||
) -> u64 {
|
||||
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
|
||||
for byte in run_id
|
||||
.bytes()
|
||||
.chain(profile_id.bytes())
|
||||
.chain(level_index.to_le_bytes())
|
||||
.chain(grid_size.to_le_bytes())
|
||||
{
|
||||
hash ^= u64::from(byte);
|
||||
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
fn map_puzzle_board_snapshot_record(board: PuzzleBoardSnapshot) -> PuzzleBoardRecord {
|
||||
PuzzleBoardRecord {
|
||||
rows: board.rows,
|
||||
cols: board.cols,
|
||||
pieces: board
|
||||
.pieces
|
||||
.into_iter()
|
||||
.map(|piece| PuzzlePieceStateRecord {
|
||||
piece_id: piece.piece_id,
|
||||
correct_row: piece.correct_row,
|
||||
correct_col: piece.correct_col,
|
||||
current_row: piece.current_row,
|
||||
current_col: piece.current_col,
|
||||
merged_group_id: piece.merged_group_id,
|
||||
})
|
||||
.collect(),
|
||||
merged_groups: board
|
||||
.merged_groups
|
||||
.into_iter()
|
||||
.map(|group| PuzzleMergedGroupRecord {
|
||||
group_id: group.group_id,
|
||||
piece_ids: group.piece_ids,
|
||||
occupied_cells: group
|
||||
.occupied_cells
|
||||
.into_iter()
|
||||
.map(|cell| PuzzleCellPositionRecord {
|
||||
row: cell.row,
|
||||
col: cell.col,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
selected_piece_id: board.selected_piece_id,
|
||||
all_tiles_resolved: board.all_tiles_resolved,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn board_positions(board: &PuzzleBoardRecord) -> Vec<(u32, u32)> {
|
||||
board
|
||||
.pieces
|
||||
.iter()
|
||||
.map(|piece| (piece.current_row, piece.current_col))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn has_original_neighbor_pair(board: &PuzzleBoardRecord) -> bool {
|
||||
board.pieces.iter().any(|piece| {
|
||||
board.pieces.iter().any(|candidate| {
|
||||
piece.piece_id != candidate.piece_id
|
||||
&& piece.current_row.abs_diff(candidate.current_row)
|
||||
+ piece.current_col.abs_diff(candidate.current_col)
|
||||
== 1
|
||||
&& piece.correct_row.abs_diff(candidate.correct_row)
|
||||
+ piece.correct_col.abs_diff(candidate.correct_col)
|
||||
== 1
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_next_level_board_shuffle_changes_by_level() {
|
||||
let second = build_local_puzzle_board(3, "run-a", "profile-level-2", 2);
|
||||
let third = build_local_puzzle_board(3, "run-a", "profile-level-3", 3);
|
||||
|
||||
assert_ne!(board_positions(&second), board_positions(&third));
|
||||
assert!(!has_original_neighbor_pair(&second));
|
||||
assert!(!has_original_neighbor_pair(&third));
|
||||
}
|
||||
}
|
||||
|
||||
struct PuzzleDashScopeSettings {
|
||||
base_url: String,
|
||||
api_key: String,
|
||||
|
||||
@@ -4,12 +4,17 @@ use axum::{
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
use module_runtime::RuntimeSnapshotRecord;
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::story::{
|
||||
BeginStorySessionRequest, ContinueStoryRequest, StoryEventPayload,
|
||||
StorySessionMutationResponse, StorySessionPayload, StorySessionStateResponse,
|
||||
BeginStoryRuntimeSessionRequest, BeginStorySessionRequest, ContinueStoryRequest,
|
||||
ResolveStoryRuntimeActionRequest, StoryEventPayload, StoryRuntimeMutationResponse,
|
||||
StoryRuntimeSnapshotPayload, StorySessionMutationResponse, StorySessionPayload,
|
||||
StorySessionStateResponse,
|
||||
};
|
||||
use shared_kernel::{offset_datetime_to_unix_micros, parse_rfc3339};
|
||||
use spacetime_client::SpacetimeClientError;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
api_response::json_success_body, auth::AuthenticatedAccessToken, http_error::AppError,
|
||||
@@ -43,28 +48,75 @@ pub async fn begin_story_session(
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
StorySessionMutationResponse {
|
||||
story_session: StorySessionPayload {
|
||||
story_session_id: result.session.story_session_id,
|
||||
runtime_session_id: result.session.runtime_session_id,
|
||||
actor_user_id: result.session.actor_user_id,
|
||||
world_profile_id: result.session.world_profile_id,
|
||||
initial_prompt: result.session.initial_prompt,
|
||||
opening_summary: result.session.opening_summary,
|
||||
latest_narrative_text: result.session.latest_narrative_text,
|
||||
latest_choice_function_id: result.session.latest_choice_function_id,
|
||||
status: result.session.status,
|
||||
version: result.session.version,
|
||||
created_at: result.session.created_at,
|
||||
updated_at: result.session.updated_at,
|
||||
},
|
||||
story_event: StoryEventPayload {
|
||||
event_id: result.event.event_id,
|
||||
story_session_id: result.event.story_session_id,
|
||||
event_kind: result.event.event_kind,
|
||||
narrative_text: result.event.narrative_text,
|
||||
choice_function_id: result.event.choice_function_id,
|
||||
created_at: result.event.created_at,
|
||||
},
|
||||
story_session: story_session_payload_from_record(result.session),
|
||||
story_event: story_event_payload_from_record(result.event),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn begin_story_runtime_session(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<BeginStoryRuntimeSessionRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let now_micros = current_utc_micros();
|
||||
let actor_user_id = authenticated.claims().user_id().to_string();
|
||||
let runtime_session_id = module_runtime_story::generate_runtime_session_id(
|
||||
actor_user_id.as_str(),
|
||||
payload.custom_world_profile.as_ref(),
|
||||
&payload.character,
|
||||
now_micros,
|
||||
);
|
||||
let story_session_id = module_story::generate_story_session_id(now_micros);
|
||||
let built = module_runtime_story::build_runtime_story_bootstrap(
|
||||
&payload,
|
||||
module_runtime_story::RuntimeStoryBootstrapSeed {
|
||||
runtime_session_id,
|
||||
story_session_id,
|
||||
actor_user_id: actor_user_id.clone(),
|
||||
now_micros,
|
||||
},
|
||||
)
|
||||
.map_err(|message| {
|
||||
story_sessions_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": message,
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
|
||||
let story_result = state
|
||||
.spacetime_client()
|
||||
.begin_story_session(
|
||||
built.story_session_id.clone(),
|
||||
built.runtime_session_id.clone(),
|
||||
actor_user_id.clone(),
|
||||
built.world_profile_id,
|
||||
built.initial_prompt,
|
||||
built.opening_summary,
|
||||
now_micros,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
story_sessions_error_response(&request_context, map_story_session_client_error(error))
|
||||
})?;
|
||||
|
||||
let persisted =
|
||||
persist_story_runtime_snapshot(&state, &request_context, actor_user_id, built.snapshot)
|
||||
.await?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
StoryRuntimeMutationResponse {
|
||||
projection: build_story_runtime_projection_from_persisted(
|
||||
story_session_payload_from_record(story_result.session),
|
||||
vec![story_event_payload_from_record(story_result.event)],
|
||||
&persisted,
|
||||
persisted.version,
|
||||
),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -108,28 +160,105 @@ pub async fn continue_story(
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
StorySessionMutationResponse {
|
||||
story_session: StorySessionPayload {
|
||||
story_session_id: result.session.story_session_id,
|
||||
runtime_session_id: result.session.runtime_session_id,
|
||||
actor_user_id: result.session.actor_user_id,
|
||||
world_profile_id: result.session.world_profile_id,
|
||||
initial_prompt: result.session.initial_prompt,
|
||||
opening_summary: result.session.opening_summary,
|
||||
latest_narrative_text: result.session.latest_narrative_text,
|
||||
latest_choice_function_id: result.session.latest_choice_function_id,
|
||||
status: result.session.status,
|
||||
version: result.session.version,
|
||||
created_at: result.session.created_at,
|
||||
updated_at: result.session.updated_at,
|
||||
},
|
||||
story_event: StoryEventPayload {
|
||||
event_id: result.event.event_id,
|
||||
story_session_id: result.event.story_session_id,
|
||||
event_kind: result.event.event_kind,
|
||||
narrative_text: result.event.narrative_text,
|
||||
choice_function_id: result.event.choice_function_id,
|
||||
created_at: result.event.created_at,
|
||||
},
|
||||
story_session: story_session_payload_from_record(result.session),
|
||||
story_event: story_event_payload_from_record(result.event),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn resolve_story_runtime_action(
|
||||
State(state): State<AppState>,
|
||||
Path(story_session_id): Path<String>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<ResolveStoryRuntimeActionRequest>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let now_micros = current_utc_micros();
|
||||
let actor_user_id = authenticated.claims().user_id().to_string();
|
||||
let story_session_id = validate_story_runtime_action_path(
|
||||
&request_context,
|
||||
story_session_id,
|
||||
payload.story_session_id.as_str(),
|
||||
)?;
|
||||
let story_state = state
|
||||
.spacetime_client()
|
||||
.get_story_session_state(story_session_id.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
story_sessions_error_response(&request_context, map_story_session_client_error(error))
|
||||
})?;
|
||||
require_story_session_owner(
|
||||
&request_context,
|
||||
&story_state.session.actor_user_id,
|
||||
&actor_user_id,
|
||||
)?;
|
||||
|
||||
let snapshot_record = state
|
||||
.get_runtime_snapshot_record(actor_user_id.clone())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
story_sessions_error_response(&request_context, map_story_session_client_error(error))
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
story_sessions_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::CONFLICT).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": "当前用户缺少 runtime snapshot",
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
let snapshot = story_runtime_snapshot_payload_from_record(&snapshot_record);
|
||||
validate_story_runtime_client_version(
|
||||
&request_context,
|
||||
payload.client_version,
|
||||
&snapshot.game_state,
|
||||
)?;
|
||||
|
||||
let resolved = module_runtime_story::resolve_story_runtime_action(
|
||||
module_runtime_story::StoryRuntimeActionResolveInput {
|
||||
story_session_id: story_state.session.story_session_id.clone(),
|
||||
runtime_session_id: story_state.session.runtime_session_id.clone(),
|
||||
snapshot,
|
||||
request: payload,
|
||||
},
|
||||
)
|
||||
.map_err(|message| {
|
||||
story_sessions_error_response(
|
||||
&request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": message,
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
|
||||
let story_result = state
|
||||
.spacetime_client()
|
||||
.continue_story(
|
||||
story_state.session.story_session_id,
|
||||
module_story::generate_story_event_id(now_micros),
|
||||
resolved.narrative_text,
|
||||
resolved.choice_function_id,
|
||||
now_micros,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
story_sessions_error_response(&request_context, map_story_session_client_error(error))
|
||||
})?;
|
||||
let persisted =
|
||||
persist_story_runtime_snapshot(&state, &request_context, actor_user_id, resolved.snapshot)
|
||||
.await?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
StoryRuntimeMutationResponse {
|
||||
projection: build_story_runtime_projection_from_persisted(
|
||||
story_session_payload_from_record(story_result.session),
|
||||
vec![story_event_payload_from_record(story_result.event)],
|
||||
&persisted,
|
||||
resolved.server_version.max(persisted.version),
|
||||
),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -157,36 +286,257 @@ pub async fn get_story_session_state(
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
StorySessionStateResponse {
|
||||
story_session: StorySessionPayload {
|
||||
story_session_id: result.session.story_session_id,
|
||||
runtime_session_id: result.session.runtime_session_id,
|
||||
actor_user_id: result.session.actor_user_id,
|
||||
world_profile_id: result.session.world_profile_id,
|
||||
initial_prompt: result.session.initial_prompt,
|
||||
opening_summary: result.session.opening_summary,
|
||||
latest_narrative_text: result.session.latest_narrative_text,
|
||||
latest_choice_function_id: result.session.latest_choice_function_id,
|
||||
status: result.session.status,
|
||||
version: result.session.version,
|
||||
created_at: result.session.created_at,
|
||||
updated_at: result.session.updated_at,
|
||||
},
|
||||
story_session: story_session_payload_from_record(result.session),
|
||||
story_events: result
|
||||
.events
|
||||
.into_iter()
|
||||
.map(|event| StoryEventPayload {
|
||||
event_id: event.event_id,
|
||||
story_session_id: event.story_session_id,
|
||||
event_kind: event.event_kind,
|
||||
narrative_text: event.narrative_text,
|
||||
choice_function_id: event.choice_function_id,
|
||||
created_at: event.created_at,
|
||||
})
|
||||
.map(story_event_payload_from_record)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
async fn persist_story_runtime_snapshot(
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
user_id: String,
|
||||
snapshot: StoryRuntimeSnapshotPayload,
|
||||
) -> Result<RuntimeSnapshotRecord, Response> {
|
||||
validate_story_runtime_snapshot_payload(&snapshot).map_err(|message| {
|
||||
story_sessions_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": message,
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let saved_at = snapshot
|
||||
.saved_at
|
||||
.as_deref()
|
||||
.and_then(module_runtime_story::normalize_required_string)
|
||||
.map(|value| parse_rfc3339(value.as_str()))
|
||||
.transpose()
|
||||
.map_err(|error| {
|
||||
story_sessions_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"field": "snapshot.savedAt",
|
||||
"message": format!("savedAt 非法: {error}"),
|
||||
})),
|
||||
)
|
||||
})?
|
||||
.unwrap_or(now);
|
||||
let saved_at_micros = offset_datetime_to_unix_micros(saved_at);
|
||||
let updated_at_micros = offset_datetime_to_unix_micros(now);
|
||||
let game_state = canonicalize_story_runtime_game_state(snapshot.game_state);
|
||||
|
||||
state
|
||||
.put_runtime_snapshot_record(
|
||||
user_id,
|
||||
saved_at_micros,
|
||||
snapshot.bottom_tab,
|
||||
game_state,
|
||||
snapshot.current_story,
|
||||
updated_at_micros,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
story_sessions_error_response(request_context, map_story_session_client_error(error))
|
||||
})
|
||||
}
|
||||
|
||||
fn canonicalize_story_runtime_game_state(mut game_state: Value) -> Value {
|
||||
if let Some(root) = game_state.as_object_mut() {
|
||||
// 中文注释:NPC 交易 / 赠礼 view 是展示层投影,持久快照只保存可复算真相。
|
||||
root.remove("runtimeNpcInteraction");
|
||||
}
|
||||
game_state
|
||||
}
|
||||
|
||||
fn validate_story_runtime_snapshot_payload(
|
||||
snapshot: &StoryRuntimeSnapshotPayload,
|
||||
) -> Result<(), String> {
|
||||
if module_runtime_story::normalize_required_string(snapshot.bottom_tab.as_str()).is_none() {
|
||||
return Err("snapshot.bottomTab 不能为空".to_string());
|
||||
}
|
||||
if !snapshot.game_state.is_object() {
|
||||
return Err("snapshot.gameState 必须是 JSON object".to_string());
|
||||
}
|
||||
if snapshot
|
||||
.current_story
|
||||
.as_ref()
|
||||
.is_some_and(|current_story| !current_story.is_object())
|
||||
{
|
||||
return Err("snapshot.currentStory 必须是 JSON object 或 null".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn story_runtime_snapshot_payload_from_record(
|
||||
record: &RuntimeSnapshotRecord,
|
||||
) -> StoryRuntimeSnapshotPayload {
|
||||
let mut game_state = record.game_state.clone();
|
||||
module_runtime_story::write_runtime_npc_interaction_view(&mut game_state);
|
||||
|
||||
StoryRuntimeSnapshotPayload {
|
||||
saved_at: Some(record.saved_at.clone()),
|
||||
bottom_tab: record.bottom_tab.clone(),
|
||||
game_state,
|
||||
current_story: record.current_story.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_story_runtime_projection_from_persisted(
|
||||
story_session: StorySessionPayload,
|
||||
story_events: Vec<StoryEventPayload>,
|
||||
record: &RuntimeSnapshotRecord,
|
||||
server_version: u32,
|
||||
) -> shared_contracts::story::StoryRuntimeProjectionResponse {
|
||||
let snapshot = story_runtime_snapshot_payload_from_record(record);
|
||||
let current_story = snapshot.current_story.as_ref();
|
||||
let options =
|
||||
module_runtime_story::build_runtime_story_options(current_story, &snapshot.game_state);
|
||||
let current_narrative_text = read_story_runtime_current_text(current_story)
|
||||
.or_else(|| Some(story_session.latest_narrative_text.clone()));
|
||||
let action_result_text = read_story_runtime_current_field(current_story, "resultText");
|
||||
let toast = read_story_runtime_current_field(current_story, "toast");
|
||||
|
||||
module_runtime_story::build_story_runtime_projection(
|
||||
module_runtime_story::StoryRuntimeProjectionSource {
|
||||
story_session,
|
||||
story_events,
|
||||
game_state: snapshot.game_state,
|
||||
options,
|
||||
server_version,
|
||||
current_narrative_text,
|
||||
action_result_text,
|
||||
toast,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn read_story_runtime_current_text(current_story: Option<&Value>) -> Option<String> {
|
||||
read_story_runtime_current_field(current_story, "text")
|
||||
.or_else(|| read_story_runtime_current_field(current_story, "storyText"))
|
||||
}
|
||||
|
||||
fn read_story_runtime_current_field(current_story: Option<&Value>, field: &str) -> Option<String> {
|
||||
current_story?
|
||||
.as_object()?
|
||||
.get(field)?
|
||||
.as_str()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
fn validate_story_runtime_action_path(
|
||||
request_context: &RequestContext,
|
||||
path_story_session_id: String,
|
||||
body_story_session_id: &str,
|
||||
) -> Result<String, Response> {
|
||||
let path_story_session_id =
|
||||
module_runtime_story::normalize_required_string(path_story_session_id.as_str())
|
||||
.ok_or_else(|| {
|
||||
story_sessions_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": "storySessionId 不能为空",
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
let body_story_session_id = module_runtime_story::normalize_required_string(
|
||||
body_story_session_id,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
story_sessions_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": "request.storySessionId 不能为空",
|
||||
})),
|
||||
)
|
||||
})?;
|
||||
|
||||
if path_story_session_id != body_story_session_id {
|
||||
return Err(story_sessions_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::CONFLICT).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": "path storySessionId 与 request.storySessionId 不一致",
|
||||
"pathStorySessionId": path_story_session_id,
|
||||
"requestStorySessionId": body_story_session_id,
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(path_story_session_id)
|
||||
}
|
||||
|
||||
fn validate_story_runtime_client_version(
|
||||
request_context: &RequestContext,
|
||||
client_version: Option<u32>,
|
||||
game_state: &Value,
|
||||
) -> Result<(), Response> {
|
||||
let Some(client_version) = client_version else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(server_version) =
|
||||
module_runtime_story::read_u32_field(game_state, "runtimeActionVersion")
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
if client_version == server_version {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(story_sessions_error_response(
|
||||
request_context,
|
||||
AppError::from_status(StatusCode::CONFLICT).with_details(json!({
|
||||
"provider": "story-runtime",
|
||||
"message": "运行时版本已变化,请先同步最新快照后再提交动作",
|
||||
"clientVersion": client_version,
|
||||
"serverVersion": server_version,
|
||||
})),
|
||||
))
|
||||
}
|
||||
|
||||
fn story_session_payload_from_record(
|
||||
record: module_story::StorySessionRecord,
|
||||
) -> StorySessionPayload {
|
||||
StorySessionPayload {
|
||||
story_session_id: record.story_session_id,
|
||||
runtime_session_id: record.runtime_session_id,
|
||||
actor_user_id: record.actor_user_id,
|
||||
world_profile_id: record.world_profile_id,
|
||||
initial_prompt: record.initial_prompt,
|
||||
opening_summary: record.opening_summary,
|
||||
latest_narrative_text: record.latest_narrative_text,
|
||||
latest_choice_function_id: record.latest_choice_function_id,
|
||||
status: record.status,
|
||||
version: record.version,
|
||||
created_at: record.created_at,
|
||||
updated_at: record.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
fn story_event_payload_from_record(record: module_story::StoryEventRecord) -> StoryEventPayload {
|
||||
StoryEventPayload {
|
||||
event_id: record.event_id,
|
||||
story_session_id: record.story_session_id,
|
||||
event_kind: record.event_kind,
|
||||
narrative_text: record.narrative_text,
|
||||
choice_function_id: record.choice_function_id,
|
||||
created_at: record.created_at,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_story_runtime_projection(
|
||||
State(state): State<AppState>,
|
||||
Path(story_session_id): Path<String>,
|
||||
@@ -373,6 +723,156 @@ mod tests {
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn begin_story_runtime_session_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/story/sessions/runtime")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"worldType": "CUSTOM",
|
||||
"customWorldProfile": { "id": "profile_001" },
|
||||
"character": { "id": "hero_001", "name": "沈砺" },
|
||||
"runtimeMode": "play",
|
||||
"disablePersistence": false
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn begin_story_runtime_session_returns_bad_gateway_when_spacetime_not_published() {
|
||||
let state = seed_authenticated_state().await;
|
||||
let token = issue_access_token(&state);
|
||||
let app = build_router(state);
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/story/sessions/runtime")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.header("content-type", "application/json")
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"worldType": "CUSTOM",
|
||||
"customWorldProfile": { "id": "profile_001" },
|
||||
"character": { "id": "hero_001", "name": "沈砺" },
|
||||
"runtimeMode": "play",
|
||||
"disablePersistence": false
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::BAD_GATEWAY);
|
||||
|
||||
let body = response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("body should collect")
|
||||
.to_bytes();
|
||||
let payload: Value =
|
||||
serde_json::from_slice(&body).expect("response body should be valid json");
|
||||
|
||||
assert_eq!(payload["ok"], Value::Bool(false));
|
||||
assert_eq!(
|
||||
payload["error"]["details"]["provider"],
|
||||
Value::String("spacetimedb".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_story_runtime_action_requires_authentication() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/story/sessions/storysess_001/actions/resolve")
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"storySessionId": "storysess_001",
|
||||
"clientVersion": 1,
|
||||
"functionId": "idle_observe_signs",
|
||||
"actionText": "观察周围迹象",
|
||||
"payload": { "optionText": "观察周围迹象" }
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_story_runtime_action_returns_bad_gateway_when_spacetime_not_published() {
|
||||
let state = seed_authenticated_state().await;
|
||||
let token = issue_access_token(&state);
|
||||
let app = build_router(state);
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/story/sessions/storysess_001/actions/resolve")
|
||||
.header("authorization", format!("Bearer {token}"))
|
||||
.header("content-type", "application/json")
|
||||
.header("x-genarrative-response-envelope", "v1")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"storySessionId": "storysess_001",
|
||||
"clientVersion": 1,
|
||||
"functionId": "idle_observe_signs",
|
||||
"actionText": "观察周围迹象",
|
||||
"payload": { "optionText": "观察周围迹象" }
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("request should succeed");
|
||||
|
||||
assert_eq!(response.status(), StatusCode::BAD_GATEWAY);
|
||||
|
||||
let body = response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("body should collect")
|
||||
.to_bytes();
|
||||
let payload: Value =
|
||||
serde_json::from_slice(&body).expect("response body should be valid json");
|
||||
|
||||
assert_eq!(payload["ok"], Value::Bool(false));
|
||||
assert_eq!(
|
||||
payload["error"]["details"]["provider"],
|
||||
Value::String("spacetimedb".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn continue_story_returns_bad_gateway_when_spacetime_not_published() {
|
||||
let state = seed_authenticated_state().await;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
## 2. 当前阶段说明
|
||||
|
||||
当前提交尚未进入完整资产状态建模,但已完成与本模块直接相关的前置基础设施与首版 schema 骨架:
|
||||
当前资产对象主链已完成后端收口:资产对象确认、实体槽位绑定、历史读取、OSS 对象确认、API facade、SpacetimeDB adapter 和资产事件表已经形成同一条后端真相链。与本模块直接相关的基础设施包括:
|
||||
|
||||
1. `api-server` 已具备 `POST /api/assets/direct-upload-tickets`
|
||||
2. `platform-oss` 已具备旧 `/generated-*` 前缀兼容的 `PostObject` 签名能力
|
||||
@@ -25,7 +25,8 @@
|
||||
- `assetobj_` ID 前缀与初始版本常量
|
||||
- `asset_entity_binding` 输入、快照、返回记录与字段校验 helper
|
||||
- `assetbind_` ID 前缀
|
||||
5. `WP-AS Assets` 资产对象类型归位已完成,领域快照、命令 DTO、应用返回 DTO 和字段错误已分别落到 DDD 骨架文件中。
|
||||
5. `WP-AS Assets` 资产对象类型归位已完成,领域快照、命令 DTO、应用返回 DTO、领域事件和字段错误已分别落到 DDD 骨架文件中。
|
||||
6. `asset_event` public event table 已承接对象确认与实体绑定变更事实,订阅端和审计流程可以感知资产主链变化。
|
||||
|
||||
当前 `asset_object` 表的字段、索引与可编码约束见:
|
||||
|
||||
@@ -33,19 +34,22 @@
|
||||
2. [../../../docs/technical/ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](../../../docs/technical/ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md)
|
||||
3. [../../../docs/technical/ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md](../../../docs/technical/ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md)
|
||||
4. [../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_OBJECT_TYPE_REHOME_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_OBJECT_TYPE_REHOME_2026-04-29.md)
|
||||
5. [../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_CHAIN_CLOSURE_2026-05-01.md](../../../docs/technical/SERVER_RS_DDD_WP_AS_ASSET_CHAIN_CLOSURE_2026-05-01.md)
|
||||
|
||||
当前还已补齐:
|
||||
|
||||
1. `AssetObjectService`
|
||||
2. 私有 bucket `HEAD Object` 后的对象确认写入
|
||||
3. 当前阶段的进程内 `asset_object` 去重存储
|
||||
4. SpacetimeDB `asset_object` / `asset_entity_binding` / `asset_event` adapter 写入
|
||||
5. Rust `spacetime-client` 资产对象确认、绑定和历史 facade
|
||||
|
||||
后续与本 package 直接相关的任务包括:
|
||||
|
||||
1. 设计 `asset_job`、`asset_object`、`asset_manifest`
|
||||
1. 设计 `asset_job` 和 `asset_manifest`
|
||||
2. 设计角色、动作、场景、精灵表相关资产表
|
||||
3. 对齐资产生成、发布、对象确认与兼容接口链路
|
||||
4. 接入 OSS 对象写入与绑定编排
|
||||
3. 对齐资产生成、发布和专业资产任务编排
|
||||
4. 新增资产生成表或专业资产任务时继续复用 OSS read-url 读取链路
|
||||
|
||||
## 3. 边界约束
|
||||
|
||||
|
||||
@@ -1,4 +1,43 @@
|
||||
//! 资产领域事件。
|
||||
//!
|
||||
//! 用于表达资产已确认、绑定已变更和资产历史投影待刷新等事实。
|
||||
//! 当前阶段暂不新增事件类型,避免在 SpacetimeDB 表未补 event table 前扩散未消费 API。
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "spacetime-types")]
|
||||
use spacetimedb::SpacetimeType;
|
||||
|
||||
/// 资产领域事件。
|
||||
///
|
||||
/// 事件只描述已经发生的轻量事实,正式资产状态仍以 `asset_object`
|
||||
/// 和 `asset_entity_binding` 为准。
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AssetDomainEvent {
|
||||
ObjectConfirmed(AssetObjectConfirmedEvent),
|
||||
EntityBindingChanged(AssetEntityBindingChangedEvent),
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AssetObjectConfirmedEvent {
|
||||
pub asset_object_id: String,
|
||||
pub asset_kind: String,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub entity_id: Option<String>,
|
||||
pub occurred_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AssetEntityBindingChangedEvent {
|
||||
pub binding_id: String,
|
||||
pub asset_object_id: String,
|
||||
pub entity_kind: String,
|
||||
pub entity_id: String,
|
||||
pub slot: String,
|
||||
pub asset_kind: String,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub occurred_at_micros: i64,
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub use domain::{
|
||||
INITIAL_ASSET_OBJECT_VERSION,
|
||||
};
|
||||
pub use errors::AssetObjectFieldError;
|
||||
pub use events::{AssetDomainEvent, AssetEntityBindingChangedEvent, AssetObjectConfirmedEvent};
|
||||
|
||||
pub use asset_object_core::{
|
||||
build_asset_entity_binding_record, build_asset_history_entry_record, build_asset_object_record,
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
4. `src/errors.rs` 承接拼图字段错误与中文错误文案。
|
||||
5. `src/events.rs` 承接草稿变化、作品发布和运行态推进的最小领域事件。
|
||||
6. `spacetime-types` feature 下可供 SpacetimeDB 绑定复用的类型派生。
|
||||
7. 正式平台 Puzzle 运行态已经接入后端真相源:结果页草稿预览、作品开局、交换、拖动、下一关和排行榜都通过 Rust API / SpacetimeDB procedure 裁决。
|
||||
8. 旧 `/api/runtime/puzzle/runs/local-next-level` 已由 `WP-DEL` 物理删除;正式 API 只保留 run scoped 的 `/api/runtime/puzzle/runs/{run_id}/next-level`。
|
||||
|
||||
当前 crate 仍然只承接:
|
||||
|
||||
@@ -40,8 +42,10 @@
|
||||
|
||||
1. [../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_ENUM_REHOME_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_ENUM_REHOME_2026-04-29.md)
|
||||
2. [../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_SPLIT_2026-04-29.md](../../../docs/technical/SERVER_RS_DDD_WP_PZ_DOMAIN_SPLIT_2026-04-29.md)
|
||||
3. [../../../docs/technical/PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md](../../../docs/technical/PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md)
|
||||
4. [../../../docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md](../../../docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md)
|
||||
3. [../../../docs/technical/SERVER_RS_DDD_WP_PZ_RUNTIME_BACKEND_TRUTH_CLOSURE_2026-05-01.md](../../../docs/technical/SERVER_RS_DDD_WP_PZ_RUNTIME_BACKEND_TRUTH_CLOSURE_2026-05-01.md)
|
||||
4. [../../../docs/technical/SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md](../../../docs/technical/SERVER_RS_DDD_WP_DEL_CLEANUP_2026-05-01.md)
|
||||
5. [../../../docs/technical/PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md](../../../docs/technical/PUZZLE_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-22.md)
|
||||
6. [../../../docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md](../../../docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md)
|
||||
|
||||
## 3. 边界约束
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
5. `src/errors.rs` 承载 runtime story 纯规则错误。
|
||||
6. `src/lib.rs` 只保留模块声明、公开导出和子模块 re-export。
|
||||
|
||||
后续 WP-RS 继续按 battle / forge / NPC / quest / presentation 的顺序,把旧 `/api/runtime/story/*` 写侧能力迁到 session scoped 新接口,并删除运行代码中的旧入口命名。
|
||||
`bootstrap.rs` 需要组装较深的运行时初始 `game_state` JSON 模板,crate 级 `recursion_limit = "512"` 仅用于支撑 `serde_json::json!` 宏展开,不承载额外领域规则。
|
||||
|
||||
当前 WP-RS 写链路已经通过 `POST /api/story/sessions/runtime` 和 `POST /api/story/sessions/{storySessionId}/actions/resolve` 收口到 session scoped 新接口。后续只按 battle / forge / NPC / quest / presentation 的顺序增强领域规则和投影,并在 `WP-DEL` 中删除运行代码不再需要的旧入口命名。
|
||||
|
||||
配套记录见 [../../../docs/technical/SERVER_RS_DDD_WP_RS_RUNTIME_STORY_DOMAIN_SPLIT_2026-04-30.md](../../../docs/technical/SERVER_RS_DDD_WP_RS_RUNTIME_STORY_DOMAIN_SPLIT_2026-04-30.md)。
|
||||
|
||||
@@ -3,26 +3,10 @@
|
||||
//! 这里组合纯领域规则并返回后端投影;真实保存、SSE 和模型调用由外层完成。
|
||||
|
||||
use serde_json::Value;
|
||||
use shared_contracts::runtime_story::{
|
||||
RuntimeBattlePresentation, RuntimeStoryOptionView, RuntimeStoryPatch,
|
||||
RuntimeStorySnapshotPayload,
|
||||
};
|
||||
use shared_contracts::runtime_story::RuntimeStoryPatch;
|
||||
|
||||
use crate::{StoryResolution, read_bool_field, read_optional_string_field};
|
||||
|
||||
pub struct RuntimeStoryActionResponseParts {
|
||||
pub requested_session_id: String,
|
||||
pub server_version: u32,
|
||||
pub snapshot: RuntimeStorySnapshotPayload,
|
||||
pub action_text: String,
|
||||
pub result_text: String,
|
||||
pub story_text: String,
|
||||
pub options: Vec<RuntimeStoryOptionView>,
|
||||
pub patches: Vec<RuntimeStoryPatch>,
|
||||
pub toast: Option<String>,
|
||||
pub battle: Option<RuntimeBattlePresentation>,
|
||||
}
|
||||
|
||||
pub fn simple_story_resolution(
|
||||
game_state: &Value,
|
||||
action_text: String,
|
||||
|
||||
@@ -58,7 +58,6 @@ fn build_request(function_id: &str, option_text: &str) -> RuntimeStoryActionRequ
|
||||
"optionText": option_text
|
||||
})),
|
||||
},
|
||||
snapshot: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1085
server-rs/crates/module-runtime-story/src/bootstrap.rs
Normal file
1085
server-rs/crates/module-runtime-story/src/bootstrap.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,12 @@
|
||||
#![recursion_limit = "512"]
|
||||
|
||||
mod application;
|
||||
mod bootstrap;
|
||||
mod commands;
|
||||
mod domain;
|
||||
mod errors;
|
||||
mod events;
|
||||
mod session_action;
|
||||
|
||||
pub mod battle;
|
||||
#[cfg(test)]
|
||||
@@ -24,6 +28,11 @@ pub use battle::{
|
||||
build_battle_runtime_story_options, inventory_item_has_usable_effect, resolve_battle_action,
|
||||
restore_player_resource,
|
||||
};
|
||||
pub use bootstrap::{
|
||||
RuntimeStoryBootstrapBuild, RuntimeStoryBootstrapSeed, build_custom_scene_preset,
|
||||
build_encounter_from_scene_npc, build_runtime_story_bootstrap, generate_runtime_session_id,
|
||||
resolve_custom_runtime_scene_id,
|
||||
};
|
||||
pub use commands::*;
|
||||
pub use core::{
|
||||
MAX_PLAYER_LEVEL, add_player_currency, add_player_inventory_items, append_active_build_buffs,
|
||||
@@ -71,6 +80,10 @@ pub use post_battle::{
|
||||
};
|
||||
pub use projection::{StoryRuntimeProjectionSource, build_story_runtime_projection};
|
||||
pub use prompt_context::{RuntimeStoryPromptContextExtras, build_runtime_story_prompt_context};
|
||||
pub use session_action::{
|
||||
StoryRuntimeActionResolveInput, StoryRuntimeActionResolveOutput, build_runtime_story_options,
|
||||
build_runtime_story_state_response_parts, resolve_story_runtime_action,
|
||||
};
|
||||
pub use story_engine::project_story_engine_after_action;
|
||||
pub use view_model::{
|
||||
build_runtime_story_companions, build_runtime_story_encounter, build_runtime_story_view_model,
|
||||
|
||||
@@ -35,6 +35,7 @@ pub fn build_story_runtime_projection(
|
||||
story_session: source.story_session,
|
||||
story_events: source.story_events,
|
||||
server_version: source.server_version,
|
||||
game_state: source.game_state.clone(),
|
||||
actor: StoryRuntimeActorProjection {
|
||||
hp: read_i32_field(&source.game_state, "playerHp").unwrap_or(0),
|
||||
max_hp: read_i32_field(&source.game_state, "playerMaxHp").unwrap_or(1),
|
||||
@@ -174,6 +175,7 @@ mod tests {
|
||||
});
|
||||
|
||||
assert_eq!(projection.story_session.story_session_id, "storysess_1");
|
||||
assert_eq!(projection.game_state["worldType"], json!("WUXIA"));
|
||||
assert_eq!(projection.actor.hp, 28);
|
||||
assert_eq!(projection.actor.currency_text, "80 铜钱");
|
||||
assert_eq!(projection.inventory.backpack_items.len(), 1);
|
||||
|
||||
1578
server-rs/crates/module-runtime-story/src/session_action.rs
Normal file
1578
server-rs/crates/module-runtime-story/src/session_action.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,21 @@ license.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
spacetime-types = ["dep:spacetimedb"]
|
||||
spacetime-types = [
|
||||
"dep:spacetimedb",
|
||||
"module-combat/spacetime-types",
|
||||
"module-inventory/spacetime-types",
|
||||
"module-progression/spacetime-types",
|
||||
"module-quest/spacetime-types",
|
||||
"module-runtime-item/spacetime-types",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
module-combat = { path = "../module-combat", default-features = false }
|
||||
module-inventory = { path = "../module-inventory", default-features = false }
|
||||
module-progression = { path = "../module-progression", default-features = false }
|
||||
module-quest = { path = "../module-quest", default-features = false }
|
||||
module-runtime-item = { path = "../module-runtime-item", default-features = false }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
shared-kernel = { path = "../shared-kernel" }
|
||||
spacetimedb = { workspace = true, optional = true }
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
1. `StorySessionSnapshot`、`StoryEventSnapshot` 等故事会话与事件快照。
|
||||
2. `begin / continue / state query` 相关输入命令的字段归一化与基础校验。
|
||||
3. 剧情会话开局、续写事件追加、版本推进和只读记录投影。
|
||||
4. 给 `spacetime-module`、`spacetime-client` 与 `api-server` 提供可复用的纯 Rust 规则。
|
||||
4. RPG gameplay 跨域结算计划:战斗胜利、任务交付和宝箱奖励先生成纯领域计划,再由 adapter 事务执行。
|
||||
5. 给 `spacetime-module`、`spacetime-client` 与 `api-server` 提供可复用的纯 Rust 规则。
|
||||
|
||||
## 2. 当前阶段说明
|
||||
|
||||
@@ -18,11 +19,11 @@
|
||||
1. `src/domain.rs`:会话领域快照、状态和值对象。
|
||||
2. `src/commands.rs`:story session scoped 输入命令与校验。
|
||||
3. `src/events.rs`:剧情事件类型、事件快照和事件 ID 生成。
|
||||
4. `src/application.rs`:快照构造、续写应用服务和读模型记录映射。
|
||||
4. `src/application.rs`:快照构造、续写应用服务、读模型记录映射和 RPG gameplay 结算计划。
|
||||
5. `src/errors.rs`:剧情字段错误与中文错误文案。
|
||||
6. `src/lib.rs`:只保留模块公开导出,保持 `module_story::*` 对外 API 稳定。
|
||||
|
||||
当前仍未扩到完整运行态动作结算。`inventory action`、NPC interaction、forge、battle、quest 等写侧闭环继续归入 `WP-RS Runtime Story 去兼容层` 和 `WP-RPG Gameplay 域` 后续切片。
|
||||
当前 `WP-RPG Gameplay 域` 已完成领域侧收口:战斗胜利、任务交付和宝箱奖励的背包、成长、章节账本组合规则不再由 `spacetime-module` 临时拼装。完整 runtime story 动作入口、前端写侧迁移和旧 contract 删除仍归 `WP-RS / WP-FE / WP-DEL`。
|
||||
|
||||
## 3. 边界约束
|
||||
|
||||
|
||||
@@ -7,6 +7,22 @@ use crate::commands::{StoryContinueInput, StorySessionInput, normalize_optional_
|
||||
use crate::domain::{INITIAL_STORY_SESSION_VERSION, StorySessionSnapshot, StorySessionStatus};
|
||||
use crate::errors::StorySessionFieldError;
|
||||
use crate::events::{StoryEventKind, StoryEventSnapshot};
|
||||
use module_combat::{BattleStateSnapshot, CombatOutcome};
|
||||
use module_inventory::{
|
||||
GrantInventoryItemInput, InventoryEquipmentSlot, InventoryItemRarity, InventoryItemSnapshot,
|
||||
InventoryItemSourceKind, InventoryMutation, InventoryMutationInput,
|
||||
generate_inventory_mutation_id, generate_inventory_slot_id,
|
||||
};
|
||||
use module_progression::{
|
||||
ChapterProgressionLedgerInput, PlayerProgressionGrantInput, PlayerProgressionGrantSource,
|
||||
};
|
||||
use module_quest::{
|
||||
QuestRecordSnapshot, QuestRewardEquipmentSlot, QuestRewardItem, QuestRewardItemRarity,
|
||||
};
|
||||
use module_runtime_item::{
|
||||
RuntimeItemEquipmentSlot, RuntimeItemRewardItemRarity, RuntimeItemRewardItemSnapshot,
|
||||
TreasureRecordSnapshot, build_inventory_item_snapshot_from_reward_item,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared_kernel::format_timestamp_micros;
|
||||
#[cfg(feature = "spacetime-types")]
|
||||
@@ -68,6 +84,17 @@ pub struct StorySessionStateRecord {
|
||||
pub events: Vec<StoryEventRecord>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RpgGameplaySettlementPlan {
|
||||
/// 背包写入计划。adapter 只负责读取当前槽位并执行 mutation,不再拼奖励物品字段。
|
||||
pub inventory_mutations: Vec<InventoryMutationInput>,
|
||||
/// 玩家经验入账计划。章节账本需要依赖执行后的等级结果,所以单独保留。
|
||||
pub progression_grant: Option<PlayerProgressionGrantInput>,
|
||||
/// 章节实际收益账本计划。若章节预算尚未初始化,adapter 会跳过而不阻断主链。
|
||||
pub chapter_ledger: Option<ChapterProgressionLedgerInput>,
|
||||
}
|
||||
|
||||
pub fn build_story_session_snapshot(input: StorySessionInput) -> StorySessionSnapshot {
|
||||
StorySessionSnapshot {
|
||||
story_session_id: input.story_session_id,
|
||||
@@ -165,3 +192,272 @@ pub fn build_story_session_state_record(
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_combat_victory_settlement_plan(
|
||||
snapshot: &BattleStateSnapshot,
|
||||
) -> RpgGameplaySettlementPlan {
|
||||
// 非胜利结果不触发战利品、敌对经验或章节 hostile 账本,避免逃脱/切磋混入击杀收益。
|
||||
if snapshot.last_outcome != CombatOutcome::Victory {
|
||||
return empty_settlement_plan();
|
||||
}
|
||||
|
||||
let inventory_mutations = snapshot
|
||||
.reward_items
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.map(|(index, reward_item)| {
|
||||
let seed = build_reward_seed(snapshot.updated_at_micros, index);
|
||||
InventoryMutationInput {
|
||||
mutation_id: generate_inventory_mutation_id(seed),
|
||||
runtime_session_id: snapshot.runtime_session_id.clone(),
|
||||
story_session_id: Some(snapshot.story_session_id.clone()),
|
||||
actor_user_id: snapshot.actor_user_id.clone(),
|
||||
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput {
|
||||
slot_id: generate_inventory_slot_id(seed),
|
||||
item: build_inventory_item_snapshot_from_battle_reward_item(
|
||||
&snapshot.battle_state_id,
|
||||
reward_item,
|
||||
),
|
||||
}),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let progression_grant = (snapshot.experience_reward > 0).then(|| PlayerProgressionGrantInput {
|
||||
user_id: snapshot.actor_user_id.clone(),
|
||||
amount: snapshot.experience_reward,
|
||||
source: PlayerProgressionGrantSource::HostileNpc,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
});
|
||||
|
||||
let chapter_ledger =
|
||||
progression_grant
|
||||
.as_ref()
|
||||
.and_then(|_| match snapshot.chapter_id.as_deref() {
|
||||
Some(chapter_id) if !chapter_id.trim().is_empty() => {
|
||||
Some(ChapterProgressionLedgerInput {
|
||||
user_id: snapshot.actor_user_id.clone(),
|
||||
chapter_id: chapter_id.trim().to_string(),
|
||||
granted_quest_xp: 0,
|
||||
granted_hostile_xp: snapshot.experience_reward,
|
||||
hostile_defeat_increment: 1,
|
||||
level_at_exit: None,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
RpgGameplaySettlementPlan {
|
||||
inventory_mutations,
|
||||
progression_grant,
|
||||
chapter_ledger,
|
||||
}
|
||||
}
|
||||
|
||||
/// 任务交付后只生成结算计划,不在领域层直接写背包、成长或章节表。
|
||||
pub fn build_quest_turn_in_settlement_plan(
|
||||
snapshot: &QuestRecordSnapshot,
|
||||
) -> RpgGameplaySettlementPlan {
|
||||
let inventory_mutations = snapshot
|
||||
.reward
|
||||
.items
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.map(|(index, reward_item)| {
|
||||
let seed = build_reward_seed(snapshot.updated_at_micros, index);
|
||||
InventoryMutationInput {
|
||||
mutation_id: generate_inventory_mutation_id(seed),
|
||||
runtime_session_id: snapshot.runtime_session_id.clone(),
|
||||
story_session_id: snapshot.story_session_id.clone(),
|
||||
actor_user_id: snapshot.actor_user_id.clone(),
|
||||
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput {
|
||||
slot_id: generate_inventory_slot_id(seed),
|
||||
item: build_inventory_item_snapshot_from_quest_reward_item(
|
||||
&snapshot.quest_id,
|
||||
reward_item,
|
||||
),
|
||||
}),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let reward_experience = snapshot.reward.experience.unwrap_or(0);
|
||||
let progression_grant = (reward_experience > 0).then(|| PlayerProgressionGrantInput {
|
||||
user_id: snapshot.actor_user_id.clone(),
|
||||
amount: reward_experience,
|
||||
source: PlayerProgressionGrantSource::Quest,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
});
|
||||
|
||||
let chapter_ledger =
|
||||
progression_grant
|
||||
.as_ref()
|
||||
.and_then(|_| match snapshot.chapter_id.as_deref() {
|
||||
Some(chapter_id) if !chapter_id.trim().is_empty() => {
|
||||
Some(ChapterProgressionLedgerInput {
|
||||
user_id: snapshot.actor_user_id.clone(),
|
||||
chapter_id: chapter_id.trim().to_string(),
|
||||
granted_quest_xp: reward_experience,
|
||||
granted_hostile_xp: 0,
|
||||
hostile_defeat_increment: 0,
|
||||
level_at_exit: None,
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
RpgGameplaySettlementPlan {
|
||||
inventory_mutations,
|
||||
progression_grant,
|
||||
chapter_ledger,
|
||||
}
|
||||
}
|
||||
|
||||
/// 宝箱记录由 `module-runtime-item` 建模,这里只把奖励转成 story gameplay 的背包写入计划。
|
||||
pub fn build_treasure_settlement_plan(
|
||||
snapshot: &TreasureRecordSnapshot,
|
||||
) -> Result<RpgGameplaySettlementPlan, StorySessionFieldError> {
|
||||
let inventory_mutations = snapshot
|
||||
.reward_items
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.map(
|
||||
|(index, reward_item)| -> Result<InventoryMutationInput, StorySessionFieldError> {
|
||||
Ok(InventoryMutationInput {
|
||||
mutation_id: build_treasure_inventory_mutation_id(
|
||||
&snapshot.treasure_record_id,
|
||||
index,
|
||||
),
|
||||
runtime_session_id: snapshot.runtime_session_id.clone(),
|
||||
story_session_id: Some(snapshot.story_session_id.clone()),
|
||||
actor_user_id: snapshot.actor_user_id.clone(),
|
||||
mutation: InventoryMutation::GrantItem(GrantInventoryItemInput {
|
||||
slot_id: build_treasure_inventory_slot_id(
|
||||
&snapshot.treasure_record_id,
|
||||
index,
|
||||
),
|
||||
item: build_inventory_item_snapshot_from_reward_item(
|
||||
&snapshot.treasure_record_id,
|
||||
reward_item,
|
||||
)
|
||||
.map_err(|_| StorySessionFieldError::InvalidGameplayReward)?,
|
||||
}),
|
||||
updated_at_micros: snapshot.updated_at_micros,
|
||||
})
|
||||
},
|
||||
)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(RpgGameplaySettlementPlan {
|
||||
inventory_mutations,
|
||||
progression_grant: None,
|
||||
chapter_ledger: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn empty_settlement_plan() -> RpgGameplaySettlementPlan {
|
||||
RpgGameplaySettlementPlan {
|
||||
inventory_mutations: Vec::new(),
|
||||
progression_grant: None,
|
||||
chapter_ledger: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_inventory_item_snapshot_from_battle_reward_item(
|
||||
battle_state_id: &str,
|
||||
reward_item: RuntimeItemRewardItemSnapshot,
|
||||
) -> InventoryItemSnapshot {
|
||||
InventoryItemSnapshot {
|
||||
item_id: reward_item.item_id,
|
||||
category: reward_item.category,
|
||||
name: reward_item.item_name,
|
||||
description: reward_item.description,
|
||||
quantity: reward_item.quantity,
|
||||
rarity: map_runtime_reward_item_rarity(reward_item.rarity),
|
||||
tags: reward_item.tags,
|
||||
stackable: reward_item.stackable,
|
||||
stack_key: reward_item.stack_key,
|
||||
equipment_slot_id: reward_item
|
||||
.equipment_slot_id
|
||||
.map(map_runtime_reward_equipment_slot),
|
||||
source_kind: InventoryItemSourceKind::CombatDrop,
|
||||
source_reference_id: Some(battle_state_id.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_inventory_item_snapshot_from_quest_reward_item(
|
||||
quest_id: &str,
|
||||
reward_item: QuestRewardItem,
|
||||
) -> InventoryItemSnapshot {
|
||||
InventoryItemSnapshot {
|
||||
item_id: reward_item.item_id,
|
||||
category: reward_item.category,
|
||||
name: reward_item.name,
|
||||
description: reward_item.description,
|
||||
quantity: reward_item.quantity,
|
||||
rarity: map_quest_reward_item_rarity(reward_item.rarity),
|
||||
tags: reward_item.tags,
|
||||
stackable: reward_item.stackable,
|
||||
stack_key: reward_item.stack_key,
|
||||
equipment_slot_id: reward_item
|
||||
.equipment_slot_id
|
||||
.map(map_quest_reward_equipment_slot),
|
||||
source_kind: InventoryItemSourceKind::QuestReward,
|
||||
source_reference_id: Some(quest_id.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_quest_reward_item_rarity(rarity: QuestRewardItemRarity) -> InventoryItemRarity {
|
||||
match rarity {
|
||||
QuestRewardItemRarity::Common => InventoryItemRarity::Common,
|
||||
QuestRewardItemRarity::Uncommon => InventoryItemRarity::Uncommon,
|
||||
QuestRewardItemRarity::Rare => InventoryItemRarity::Rare,
|
||||
QuestRewardItemRarity::Epic => InventoryItemRarity::Epic,
|
||||
QuestRewardItemRarity::Legendary => InventoryItemRarity::Legendary,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_runtime_reward_item_rarity(rarity: RuntimeItemRewardItemRarity) -> InventoryItemRarity {
|
||||
match rarity {
|
||||
RuntimeItemRewardItemRarity::Common => InventoryItemRarity::Common,
|
||||
RuntimeItemRewardItemRarity::Uncommon => InventoryItemRarity::Uncommon,
|
||||
RuntimeItemRewardItemRarity::Rare => InventoryItemRarity::Rare,
|
||||
RuntimeItemRewardItemRarity::Epic => InventoryItemRarity::Epic,
|
||||
RuntimeItemRewardItemRarity::Legendary => InventoryItemRarity::Legendary,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_quest_reward_equipment_slot(slot: QuestRewardEquipmentSlot) -> InventoryEquipmentSlot {
|
||||
match slot {
|
||||
QuestRewardEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
|
||||
QuestRewardEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
|
||||
QuestRewardEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_runtime_reward_equipment_slot(slot: RuntimeItemEquipmentSlot) -> InventoryEquipmentSlot {
|
||||
match slot {
|
||||
RuntimeItemEquipmentSlot::Weapon => InventoryEquipmentSlot::Weapon,
|
||||
RuntimeItemEquipmentSlot::Armor => InventoryEquipmentSlot::Armor,
|
||||
RuntimeItemEquipmentSlot::Relic => InventoryEquipmentSlot::Relic,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_reward_seed(updated_at_micros: i64, index: usize) -> i64 {
|
||||
updated_at_micros.saturating_add(index as i64 + 1)
|
||||
}
|
||||
|
||||
fn build_treasure_inventory_slot_id(treasure_record_id: &str, reward_index: usize) -> String {
|
||||
format!("invslot_{}_{}", treasure_record_id, reward_index)
|
||||
}
|
||||
|
||||
fn build_treasure_inventory_mutation_id(treasure_record_id: &str, reward_index: usize) -> String {
|
||||
format!("invmut_{}_{}", treasure_record_id, reward_index)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub enum StorySessionFieldError {
|
||||
MissingNarrativeText,
|
||||
MissingEventId,
|
||||
InvalidVersion,
|
||||
InvalidGameplayReward,
|
||||
}
|
||||
|
||||
impl fmt::Display for StorySessionFieldError {
|
||||
@@ -29,6 +30,7 @@ impl fmt::Display for StorySessionFieldError {
|
||||
Self::MissingNarrativeText => f.write_str("story_event.narrative_text 不能为空"),
|
||||
Self::MissingEventId => f.write_str("story_event.event_id 不能为空"),
|
||||
Self::InvalidVersion => f.write_str("story_session.version 必须大于 0"),
|
||||
Self::InvalidGameplayReward => f.write_str("RPG 结算奖励字段非法"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ pub use events::*;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use module_quest::{
|
||||
QuestNarrativeBindingSnapshot, QuestNarrativeOrigin, QuestNarrativeType,
|
||||
QuestObjectiveKind, QuestObjectiveSnapshot, QuestRecordSnapshot, QuestRewardEquipmentSlot,
|
||||
QuestRewardItem, QuestRewardItemRarity, QuestRewardSnapshot, QuestStatus,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn validate_story_session_input_accepts_minimal_contract() {
|
||||
@@ -200,4 +205,172 @@ mod tests {
|
||||
assert_eq!(next.version, INITIAL_STORY_SESSION_VERSION + 1);
|
||||
assert_eq!(event.event_kind, StoryEventKind::StoryContinued);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combat_victory_settlement_plan_grants_items_xp_and_chapter_ledger() {
|
||||
let plan = build_combat_victory_settlement_plan(&module_combat::BattleStateSnapshot {
|
||||
battle_state_id: "battle_001".to_string(),
|
||||
story_session_id: "storysess_001".to_string(),
|
||||
runtime_session_id: "runtime_001".to_string(),
|
||||
actor_user_id: "user_001".to_string(),
|
||||
chapter_id: Some("chapter_01".to_string()),
|
||||
target_npc_id: "npc_bandit".to_string(),
|
||||
target_name: "山匪".to_string(),
|
||||
battle_mode: module_combat::BattleMode::Fight,
|
||||
status: module_combat::BattleStatus::Resolved,
|
||||
player_hp: 20,
|
||||
player_max_hp: 30,
|
||||
player_mana: 6,
|
||||
player_max_mana: 10,
|
||||
target_hp: 0,
|
||||
target_max_hp: 18,
|
||||
experience_reward: 35,
|
||||
reward_items: vec![module_runtime_item::RuntimeItemRewardItemSnapshot {
|
||||
item_id: "iron_token".to_string(),
|
||||
category: "material".to_string(),
|
||||
item_name: "铁制信物".to_string(),
|
||||
description: Some("山匪随身携带的旧信物。".to_string()),
|
||||
quantity: 1,
|
||||
rarity: module_runtime_item::RuntimeItemRewardItemRarity::Uncommon,
|
||||
tags: vec!["quest".to_string()],
|
||||
stackable: true,
|
||||
stack_key: "iron_token".to_string(),
|
||||
equipment_slot_id: None,
|
||||
}],
|
||||
turn_index: 2,
|
||||
last_action_function_id: Some("battle_use_skill".to_string()),
|
||||
last_action_text: Some("破阵一击".to_string()),
|
||||
last_result_text: Some("战斗结束。".to_string()),
|
||||
last_damage_dealt: 18,
|
||||
last_damage_taken: 0,
|
||||
last_outcome: module_combat::CombatOutcome::Victory,
|
||||
version: 3,
|
||||
created_at_micros: 100,
|
||||
updated_at_micros: 200,
|
||||
});
|
||||
|
||||
assert_eq!(plan.inventory_mutations.len(), 1);
|
||||
assert_eq!(
|
||||
plan.progression_grant.as_ref().map(|input| input.amount),
|
||||
Some(35)
|
||||
);
|
||||
assert_eq!(
|
||||
plan.chapter_ledger
|
||||
.as_ref()
|
||||
.map(|input| input.granted_hostile_xp),
|
||||
Some(35)
|
||||
);
|
||||
|
||||
let module_inventory::InventoryMutation::GrantItem(input) =
|
||||
&plan.inventory_mutations[0].mutation
|
||||
else {
|
||||
panic!("combat victory should grant inventory item");
|
||||
};
|
||||
assert_eq!(
|
||||
input.item.source_reference_id.as_deref(),
|
||||
Some("battle_001")
|
||||
);
|
||||
assert_eq!(
|
||||
input.item.source_kind,
|
||||
module_inventory::InventoryItemSourceKind::CombatDrop
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quest_turn_in_settlement_plan_grants_reward_items_and_quest_xp() {
|
||||
let plan = build_quest_turn_in_settlement_plan(&QuestRecordSnapshot {
|
||||
quest_id: "quest_001".to_string(),
|
||||
runtime_session_id: "runtime_001".to_string(),
|
||||
story_session_id: Some("storysess_001".to_string()),
|
||||
actor_user_id: "user_001".to_string(),
|
||||
issuer_npc_id: "npc_scout".to_string(),
|
||||
issuer_npc_name: "斥候".to_string(),
|
||||
scene_id: None,
|
||||
chapter_id: Some("chapter_01".to_string()),
|
||||
act_id: None,
|
||||
thread_id: None,
|
||||
contract_id: None,
|
||||
title: "寻找信物".to_string(),
|
||||
description: "找回遗失的信物。".to_string(),
|
||||
summary: "信物已找到。".to_string(),
|
||||
objective: QuestObjectiveSnapshot {
|
||||
kind: QuestObjectiveKind::InspectTreasure,
|
||||
target_hostile_npc_id: None,
|
||||
target_npc_id: None,
|
||||
target_scene_id: Some("scene_ruin".to_string()),
|
||||
target_item_id: None,
|
||||
required_count: 1,
|
||||
},
|
||||
progress: 1,
|
||||
status: QuestStatus::TurnedIn,
|
||||
completion_notified: true,
|
||||
reward: QuestRewardSnapshot {
|
||||
affinity_bonus: 0,
|
||||
currency: 0,
|
||||
experience: Some(40),
|
||||
items: vec![QuestRewardItem {
|
||||
item_id: "scout_badge".to_string(),
|
||||
category: "trinket".to_string(),
|
||||
name: "斥候徽记".to_string(),
|
||||
description: None,
|
||||
quantity: 1,
|
||||
rarity: QuestRewardItemRarity::Rare,
|
||||
tags: vec!["badge".to_string()],
|
||||
stackable: false,
|
||||
stack_key: "scout_badge".to_string(),
|
||||
equipment_slot_id: Some(QuestRewardEquipmentSlot::Relic),
|
||||
}],
|
||||
intel: None,
|
||||
story_hint: None,
|
||||
},
|
||||
reward_text: "获得斥候徽记。".to_string(),
|
||||
narrative_binding: QuestNarrativeBindingSnapshot {
|
||||
origin: QuestNarrativeOrigin::FallbackBuilder,
|
||||
narrative_type: QuestNarrativeType::Retrieval,
|
||||
dramatic_need: String::new(),
|
||||
issuer_goal: String::new(),
|
||||
player_hook: String::new(),
|
||||
world_reason: String::new(),
|
||||
followup_hooks: Vec::new(),
|
||||
},
|
||||
steps: Vec::new(),
|
||||
active_step_id: None,
|
||||
visible_stage: 1,
|
||||
hidden_flags: Vec::new(),
|
||||
discovered_fact_ids: Vec::new(),
|
||||
related_carrier_ids: Vec::new(),
|
||||
consequence_ids: Vec::new(),
|
||||
created_at_micros: 100,
|
||||
updated_at_micros: 300,
|
||||
completed_at_micros: Some(200),
|
||||
turned_in_at_micros: Some(300),
|
||||
});
|
||||
|
||||
assert_eq!(plan.inventory_mutations.len(), 1);
|
||||
assert_eq!(
|
||||
plan.progression_grant.as_ref().map(|input| input.amount),
|
||||
Some(40)
|
||||
);
|
||||
assert_eq!(
|
||||
plan.chapter_ledger
|
||||
.as_ref()
|
||||
.map(|input| input.granted_quest_xp),
|
||||
Some(40)
|
||||
);
|
||||
|
||||
let module_inventory::InventoryMutation::GrantItem(input) =
|
||||
&plan.inventory_mutations[0].mutation
|
||||
else {
|
||||
panic!("quest turn-in should grant inventory item");
|
||||
};
|
||||
assert_eq!(input.item.source_reference_id.as_deref(), Some("quest_001"));
|
||||
assert_eq!(
|
||||
input.item.equipment_slot_id,
|
||||
Some(module_inventory::InventoryEquipmentSlot::Relic)
|
||||
);
|
||||
assert_eq!(
|
||||
input.item.source_kind,
|
||||
module_inventory::InventoryItemSourceKind::QuestReward
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,15 @@
|
||||
2. `assets/character-animation/jobs/:taskId`
|
||||
3. `assets/character-animation/publish`
|
||||
|
||||
当前阶段新增 Stage5 `runtime story` 兼容桥 DTO 基线:
|
||||
当前阶段新增 Stage5 story session scoped runtime story 写读 DTO:
|
||||
|
||||
1. `runtime/story/state/resolve` 请求 DTO
|
||||
2. `runtime/story/actions/resolve`、`runtime/story/initial`、`runtime/story/continue` 请求 DTO
|
||||
2. `RuntimeStoryActionResponse` 兼容响应 DTO
|
||||
3. `RuntimeStoryViewModel / presentation / patches / snapshot` 显式结构
|
||||
4. `RuntimeStoryAiResponse` 兼容响应 DTO
|
||||
1. `BeginStoryRuntimeSessionRequest`
|
||||
2. `ResolveStoryRuntimeActionRequest`
|
||||
3. `StoryRuntimeProjectionResponse`
|
||||
4. `StoryRuntimeMutationResponse`
|
||||
5. `StoryRuntimeSnapshotPayload` 仅用于 story session scoped 写侧持久化边界,不复用旧 `/api/runtime/story/*` 总入口 contract。
|
||||
|
||||
`WP-DEL` 已删除旧 runtime story HTTP DTO:`RuntimeStoryStateResolveRequest`、`RuntimeStoryBootstrapRequest/Response`、`RuntimeStoryActionResponse` 和旧 `RuntimeStorySnapshotPayload`。`runtime_story` 模块中仍保留的 `RuntimeStoryViewModel`、`RuntimeStoryPresentation`、`RuntimeStoryPatch` 与 battle presentation 是当前投影/表现构件,不作为旧 HTTP 写入口。
|
||||
|
||||
当前仍刻意未做:
|
||||
|
||||
|
||||
@@ -6,14 +6,6 @@ pub struct StartPuzzleRunRequest {
|
||||
pub profile_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AdvanceLocalPuzzleNextLevelRequest {
|
||||
pub run: PuzzleRunSnapshotResponse,
|
||||
#[serde(default)]
|
||||
pub source_session_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SwapPuzzlePiecesRequest {
|
||||
|
||||
@@ -1,48 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStorySnapshotPayload {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub saved_at: Option<String>,
|
||||
pub bottom_tab: String,
|
||||
pub game_state: Value,
|
||||
#[serde(default)]
|
||||
pub current_story: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryStateResolveRequest {
|
||||
pub session_id: String,
|
||||
#[serde(default)]
|
||||
pub client_version: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub snapshot: Option<RuntimeStorySnapshotPayload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryBootstrapRequest {
|
||||
pub world_type: String,
|
||||
#[serde(default)]
|
||||
pub custom_world_profile: Option<Value>,
|
||||
pub character: Value,
|
||||
#[serde(default)]
|
||||
pub runtime_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
pub disable_persistence: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryBootstrapResponse {
|
||||
pub session_id: String,
|
||||
pub server_version: u32,
|
||||
pub snapshot: RuntimeStorySnapshotPayload,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryChoiceAction {
|
||||
@@ -62,8 +20,6 @@ pub struct RuntimeStoryActionRequest {
|
||||
#[serde(default)]
|
||||
pub client_version: Option<u32>,
|
||||
pub action: RuntimeStoryChoiceAction,
|
||||
#[serde(default)]
|
||||
pub snapshot: Option<RuntimeStorySnapshotPayload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -404,43 +360,11 @@ pub enum RuntimeStoryPatch {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RuntimeStoryActionResponse {
|
||||
pub session_id: String,
|
||||
pub server_version: u32,
|
||||
pub view_model: RuntimeStoryViewModel,
|
||||
pub presentation: RuntimeStoryPresentation,
|
||||
pub patches: Vec<RuntimeStoryPatch>,
|
||||
pub snapshot: RuntimeStorySnapshotPayload,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn runtime_story_state_resolve_request_accepts_missing_saved_at() {
|
||||
let payload: RuntimeStoryStateResolveRequest = serde_json::from_value(json!({
|
||||
"sessionId": "runtime-main",
|
||||
"clientVersion": 7,
|
||||
"snapshot": {
|
||||
"bottomTab": "adventure",
|
||||
"gameState": { "runtimeSessionId": "runtime-main" },
|
||||
"currentStory": { "text": "营地里的火光还没有熄灭。" }
|
||||
}
|
||||
}))
|
||||
.expect("payload should deserialize");
|
||||
|
||||
assert_eq!(payload.session_id, "runtime-main");
|
||||
assert_eq!(payload.client_version, Some(7));
|
||||
assert_eq!(
|
||||
payload.snapshot.expect("snapshot should exist").saved_at,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_action_request_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(RuntimeStoryActionRequest {
|
||||
@@ -452,12 +376,6 @@ mod tests {
|
||||
target_id: Some("npc_camp_firekeeper".to_string()),
|
||||
payload: Some(json!({ "optionText": "继续交谈" })),
|
||||
},
|
||||
snapshot: Some(RuntimeStorySnapshotPayload {
|
||||
saved_at: Some("2026-04-22T12:00:00.000Z".to_string()),
|
||||
bottom_tab: "adventure".to_string(),
|
||||
game_state: json!({ "runtimeSessionId": "runtime-main" }),
|
||||
current_story: None,
|
||||
}),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
@@ -466,27 +384,6 @@ mod tests {
|
||||
assert_eq!(payload["action"]["type"], json!("story_choice"));
|
||||
assert_eq!(payload["action"]["functionId"], json!("npc_chat"));
|
||||
assert_eq!(payload["action"]["targetId"], json!("npc_camp_firekeeper"));
|
||||
assert_eq!(
|
||||
payload["snapshot"]["savedAt"],
|
||||
json!("2026-04-22T12:00:00.000Z")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_bootstrap_request_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(RuntimeStoryBootstrapRequest {
|
||||
world_type: "CUSTOM".to_string(),
|
||||
custom_world_profile: Some(json!({ "id": "profile-1" })),
|
||||
character: json!({ "id": "role-1", "name": "沈砺" }),
|
||||
runtime_mode: Some("play".to_string()),
|
||||
disable_persistence: Some(false),
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(payload["worldType"], json!("CUSTOM"));
|
||||
assert_eq!(payload["customWorldProfile"]["id"], json!("profile-1"));
|
||||
assert_eq!(payload["runtimeMode"], json!("play"));
|
||||
assert_eq!(payload["disablePersistence"], json!(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -532,37 +429,157 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_story_action_response_uses_camel_case_fields() {
|
||||
let payload = serde_json::to_value(RuntimeStoryActionResponse {
|
||||
session_id: "runtime-main".to_string(),
|
||||
server_version: 8,
|
||||
view_model: RuntimeStoryViewModel {
|
||||
player: RuntimeStoryPlayerViewModel {
|
||||
hp: 32,
|
||||
max_hp: 40,
|
||||
mana: 18,
|
||||
max_mana: 20,
|
||||
},
|
||||
encounter: Some(RuntimeStoryEncounterViewModel {
|
||||
id: "npc_camp_firekeeper".to_string(),
|
||||
kind: "npc".to_string(),
|
||||
npc_name: "守火人".to_string(),
|
||||
hostile: false,
|
||||
affinity: Some(12),
|
||||
recruited: Some(false),
|
||||
interaction_active: true,
|
||||
battle_mode: None,
|
||||
}),
|
||||
companions: vec![RuntimeStoryCompanionViewModel {
|
||||
npc_id: "npc_companion_001".to_string(),
|
||||
character_id: Some("char_companion_001".to_string()),
|
||||
joined_at_affinity: 64,
|
||||
fn runtime_story_presentation_uses_camel_case_fields() {
|
||||
let view_model = RuntimeStoryViewModel {
|
||||
player: RuntimeStoryPlayerViewModel {
|
||||
hp: 32,
|
||||
max_hp: 40,
|
||||
mana: 18,
|
||||
max_mana: 20,
|
||||
},
|
||||
encounter: Some(RuntimeStoryEncounterViewModel {
|
||||
id: "npc_camp_firekeeper".to_string(),
|
||||
kind: "npc".to_string(),
|
||||
npc_name: "守火人".to_string(),
|
||||
hostile: false,
|
||||
affinity: Some(12),
|
||||
recruited: Some(false),
|
||||
interaction_active: true,
|
||||
battle_mode: None,
|
||||
}),
|
||||
companions: vec![RuntimeStoryCompanionViewModel {
|
||||
npc_id: "npc_companion_001".to_string(),
|
||||
character_id: Some("char_companion_001".to_string()),
|
||||
joined_at_affinity: 64,
|
||||
}],
|
||||
inventory: RuntimeStoryInventoryViewModel {
|
||||
player_currency: 80,
|
||||
currency_text: "80 铜钱".to_string(),
|
||||
in_battle: false,
|
||||
backpack_items: vec![RuntimeStoryInventoryItemView {
|
||||
item: json!({
|
||||
"id": "potion-1",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
actions: RuntimeStoryInventoryItemActionsView {
|
||||
use_item: RuntimeStoryInventoryActionView {
|
||||
function_id: "inventory_use".to_string(),
|
||||
action_text: "使用疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: true,
|
||||
reason: None,
|
||||
},
|
||||
equip: RuntimeStoryInventoryActionView {
|
||||
function_id: "equipment_equip".to_string(),
|
||||
action_text: "装备疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能装备。".to_string()),
|
||||
},
|
||||
dismantle: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_dismantle".to_string(),
|
||||
action_text: "拆解疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能拆解。".to_string()),
|
||||
},
|
||||
reforge: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_reforge".to_string(),
|
||||
action_text: "重铸疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能重铸。".to_string()),
|
||||
},
|
||||
},
|
||||
}],
|
||||
inventory: RuntimeStoryInventoryViewModel {
|
||||
player_currency: 80,
|
||||
currency_text: "80 铜钱".to_string(),
|
||||
in_battle: false,
|
||||
backpack_items: vec![RuntimeStoryInventoryItemView {
|
||||
equipment_slots: vec![RuntimeStoryEquipmentSlotView {
|
||||
slot_id: "weapon".to_string(),
|
||||
label: "武器".to_string(),
|
||||
item: None,
|
||||
unequip: RuntimeStoryInventoryActionView {
|
||||
function_id: "equipment_unequip".to_string(),
|
||||
action_text: "卸下武器".to_string(),
|
||||
payload: Some(json!({ "slotId": "weapon" })),
|
||||
enabled: false,
|
||||
reason: Some("武器位当前没有装备。".to_string()),
|
||||
},
|
||||
}],
|
||||
forge_recipes: vec![RuntimeStoryForgeRecipeView {
|
||||
id: "synthesis-refined-ingot".to_string(),
|
||||
name: "压炼锭材".to_string(),
|
||||
kind: "synthesis".to_string(),
|
||||
description: "把零散残片和基础材料压成稳定可用的金属锭材。".to_string(),
|
||||
result_label: "精炼锭材".to_string(),
|
||||
currency_cost: 18,
|
||||
currency_text: "18 铜钱".to_string(),
|
||||
requirements: vec![RuntimeStoryForgeRequirementView {
|
||||
id: "material:any".to_string(),
|
||||
label: "任意材料".to_string(),
|
||||
quantity: 3,
|
||||
owned: 0,
|
||||
}],
|
||||
can_craft: false,
|
||||
disabled_reason: Some("材料不足。".to_string()),
|
||||
action: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_craft".to_string(),
|
||||
action_text: "制作精炼锭材".to_string(),
|
||||
payload: Some(json!({ "recipeId": "synthesis-refined-ingot" })),
|
||||
enabled: false,
|
||||
reason: Some("材料不足。".to_string()),
|
||||
},
|
||||
}],
|
||||
},
|
||||
available_options: vec![RuntimeStoryOptionView {
|
||||
function_id: "npc_chat".to_string(),
|
||||
action_text: "继续交谈".to_string(),
|
||||
detail_text: Some("围绕当前话题继续推进关系判断。".to_string()),
|
||||
scope: "npc".to_string(),
|
||||
interaction: Some(RuntimeStoryOptionInteraction::Npc {
|
||||
npc_id: "npc_camp_firekeeper".to_string(),
|
||||
action: "chat".to_string(),
|
||||
quest_id: None,
|
||||
}),
|
||||
payload: Some(json!({ "note": "server-runtime-test" })),
|
||||
disabled: None,
|
||||
reason: None,
|
||||
}],
|
||||
status: RuntimeStoryStatusViewModel {
|
||||
in_battle: false,
|
||||
npc_interaction_active: true,
|
||||
current_npc_battle_mode: None,
|
||||
current_npc_battle_outcome: None,
|
||||
},
|
||||
npc_interaction: Some(RuntimeNpcInteractionView {
|
||||
npc_id: "npc_camp_firekeeper".to_string(),
|
||||
npc_name: "守火人".to_string(),
|
||||
player_currency: 80,
|
||||
currency_name: "铜钱".to_string(),
|
||||
trade: RuntimeNpcTradeView {
|
||||
buy_items: vec![RuntimeNpcTradeItemView {
|
||||
item_id: "npc-potion".to_string(),
|
||||
item: json!({
|
||||
"id": "npc-potion",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
mode: "buy".to_string(),
|
||||
unit_price: 20,
|
||||
max_quantity: 2,
|
||||
can_submit: true,
|
||||
reason: None,
|
||||
}],
|
||||
sell_items: Vec::new(),
|
||||
},
|
||||
gift: RuntimeNpcGiftView {
|
||||
items: vec![RuntimeNpcGiftItemView {
|
||||
item_id: "potion-1".to_string(),
|
||||
item: json!({
|
||||
"id": "potion-1",
|
||||
"name": "疗伤药",
|
||||
@@ -571,176 +588,47 @@ mod tests {
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
actions: RuntimeStoryInventoryItemActionsView {
|
||||
use_item: RuntimeStoryInventoryActionView {
|
||||
function_id: "inventory_use".to_string(),
|
||||
action_text: "使用疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: true,
|
||||
reason: None,
|
||||
},
|
||||
equip: RuntimeStoryInventoryActionView {
|
||||
function_id: "equipment_equip".to_string(),
|
||||
action_text: "装备疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能装备。".to_string()),
|
||||
},
|
||||
dismantle: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_dismantle".to_string(),
|
||||
action_text: "拆解疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能拆解。".to_string()),
|
||||
},
|
||||
reforge: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_reforge".to_string(),
|
||||
action_text: "重铸疗伤药".to_string(),
|
||||
payload: Some(json!({ "itemId": "potion-1" })),
|
||||
enabled: false,
|
||||
reason: Some("该物品不能重铸。".to_string()),
|
||||
},
|
||||
},
|
||||
}],
|
||||
equipment_slots: vec![RuntimeStoryEquipmentSlotView {
|
||||
slot_id: "weapon".to_string(),
|
||||
label: "武器".to_string(),
|
||||
item: None,
|
||||
unequip: RuntimeStoryInventoryActionView {
|
||||
function_id: "equipment_unequip".to_string(),
|
||||
action_text: "卸下武器".to_string(),
|
||||
payload: Some(json!({ "slotId": "weapon" })),
|
||||
enabled: false,
|
||||
reason: Some("武器位当前没有装备。".to_string()),
|
||||
},
|
||||
}],
|
||||
forge_recipes: vec![RuntimeStoryForgeRecipeView {
|
||||
id: "synthesis-refined-ingot".to_string(),
|
||||
name: "压炼锭材".to_string(),
|
||||
kind: "synthesis".to_string(),
|
||||
description: "把零散残片和基础材料压成稳定可用的金属锭材。".to_string(),
|
||||
result_label: "精炼锭材".to_string(),
|
||||
currency_cost: 18,
|
||||
currency_text: "18 铜钱".to_string(),
|
||||
requirements: vec![RuntimeStoryForgeRequirementView {
|
||||
id: "material:any".to_string(),
|
||||
label: "任意材料".to_string(),
|
||||
quantity: 3,
|
||||
owned: 0,
|
||||
}],
|
||||
can_craft: false,
|
||||
disabled_reason: Some("材料不足。".to_string()),
|
||||
action: RuntimeStoryInventoryActionView {
|
||||
function_id: "forge_craft".to_string(),
|
||||
action_text: "制作精炼锭材".to_string(),
|
||||
payload: Some(json!({ "recipeId": "synthesis-refined-ingot" })),
|
||||
enabled: false,
|
||||
reason: Some("材料不足。".to_string()),
|
||||
},
|
||||
affinity_gain: 10,
|
||||
can_submit: true,
|
||||
reason: None,
|
||||
}],
|
||||
},
|
||||
available_options: vec![RuntimeStoryOptionView {
|
||||
function_id: "npc_chat".to_string(),
|
||||
action_text: "继续交谈".to_string(),
|
||||
detail_text: Some("围绕当前话题继续推进关系判断。".to_string()),
|
||||
scope: "npc".to_string(),
|
||||
interaction: Some(RuntimeStoryOptionInteraction::Npc {
|
||||
npc_id: "npc_camp_firekeeper".to_string(),
|
||||
action: "chat".to_string(),
|
||||
quest_id: None,
|
||||
}),
|
||||
payload: Some(json!({ "note": "server-runtime-test" })),
|
||||
disabled: None,
|
||||
reason: None,
|
||||
}],
|
||||
status: RuntimeStoryStatusViewModel {
|
||||
in_battle: false,
|
||||
npc_interaction_active: true,
|
||||
current_npc_battle_mode: None,
|
||||
current_npc_battle_outcome: None,
|
||||
},
|
||||
npc_interaction: Some(RuntimeNpcInteractionView {
|
||||
}),
|
||||
};
|
||||
let presentation = RuntimeStoryPresentation {
|
||||
action_text: "".to_string(),
|
||||
result_text: "".to_string(),
|
||||
story_text: "守火人抬眼看了你一瞬,示意你把想问的话继续说完。".to_string(),
|
||||
options: vec![RuntimeStoryOptionView {
|
||||
function_id: "npc_chat".to_string(),
|
||||
action_text: "继续交谈".to_string(),
|
||||
detail_text: Some("围绕当前话题继续推进关系判断。".to_string()),
|
||||
scope: "npc".to_string(),
|
||||
interaction: Some(RuntimeStoryOptionInteraction::Npc {
|
||||
npc_id: "npc_camp_firekeeper".to_string(),
|
||||
npc_name: "守火人".to_string(),
|
||||
player_currency: 80,
|
||||
currency_name: "铜钱".to_string(),
|
||||
trade: RuntimeNpcTradeView {
|
||||
buy_items: vec![RuntimeNpcTradeItemView {
|
||||
item_id: "npc-potion".to_string(),
|
||||
item: json!({
|
||||
"id": "npc-potion",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
mode: "buy".to_string(),
|
||||
unit_price: 20,
|
||||
max_quantity: 2,
|
||||
can_submit: true,
|
||||
reason: None,
|
||||
}],
|
||||
sell_items: Vec::new(),
|
||||
},
|
||||
gift: RuntimeNpcGiftView {
|
||||
items: vec![RuntimeNpcGiftItemView {
|
||||
item_id: "potion-1".to_string(),
|
||||
item: json!({
|
||||
"id": "potion-1",
|
||||
"name": "疗伤药",
|
||||
"category": "消耗品",
|
||||
"quantity": 2,
|
||||
"rarity": "common",
|
||||
"tags": ["healing"]
|
||||
}),
|
||||
affinity_gain: 10,
|
||||
can_submit: true,
|
||||
reason: None,
|
||||
}],
|
||||
},
|
||||
action: "chat".to_string(),
|
||||
quest_id: None,
|
||||
}),
|
||||
},
|
||||
presentation: RuntimeStoryPresentation {
|
||||
action_text: "".to_string(),
|
||||
result_text: "".to_string(),
|
||||
story_text: "守火人抬眼看了你一瞬,示意你把想问的话继续说完。".to_string(),
|
||||
options: vec![RuntimeStoryOptionView {
|
||||
function_id: "npc_chat".to_string(),
|
||||
action_text: "继续交谈".to_string(),
|
||||
detail_text: Some("围绕当前话题继续推进关系判断。".to_string()),
|
||||
scope: "npc".to_string(),
|
||||
interaction: Some(RuntimeStoryOptionInteraction::Npc {
|
||||
npc_id: "npc_camp_firekeeper".to_string(),
|
||||
action: "chat".to_string(),
|
||||
quest_id: None,
|
||||
}),
|
||||
payload: Some(json!({ "note": "server-runtime-test" })),
|
||||
disabled: None,
|
||||
reason: None,
|
||||
}],
|
||||
toast: None,
|
||||
battle: None,
|
||||
},
|
||||
patches: vec![RuntimeStoryPatch::StatusChanged {
|
||||
in_battle: false,
|
||||
npc_interaction_active: true,
|
||||
current_npc_battle_mode: None,
|
||||
current_npc_battle_outcome: None,
|
||||
payload: Some(json!({ "note": "server-runtime-test" })),
|
||||
disabled: None,
|
||||
reason: None,
|
||||
}],
|
||||
snapshot: RuntimeStorySnapshotPayload {
|
||||
saved_at: Some("2026-04-22T12:00:00.000Z".to_string()),
|
||||
bottom_tab: "adventure".to_string(),
|
||||
game_state: json!({ "runtimeSessionId": "runtime-main" }),
|
||||
current_story: Some(json!({
|
||||
"text": "守火人抬眼看了你一瞬,示意你把想问的话继续说完。"
|
||||
})),
|
||||
},
|
||||
})
|
||||
toast: None,
|
||||
battle: None,
|
||||
};
|
||||
let patches = vec![RuntimeStoryPatch::StatusChanged {
|
||||
in_battle: false,
|
||||
npc_interaction_active: true,
|
||||
current_npc_battle_mode: None,
|
||||
current_npc_battle_outcome: None,
|
||||
}];
|
||||
let payload = serde_json::to_value(json!({
|
||||
"viewModel": view_model,
|
||||
"presentation": presentation,
|
||||
"patches": patches
|
||||
}))
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(payload["sessionId"], json!("runtime-main"));
|
||||
assert_eq!(payload["serverVersion"], json!(8));
|
||||
assert_eq!(payload["viewModel"]["player"]["maxHp"], json!(40));
|
||||
assert_eq!(
|
||||
payload["viewModel"]["availableOptions"][0]["interaction"]["npcId"],
|
||||
@@ -759,6 +647,5 @@ mod tests {
|
||||
json!("守火人抬眼看了你一瞬,示意你把想问的话继续说完。")
|
||||
);
|
||||
assert_eq!(payload["patches"][0]["type"], json!("status_changed"));
|
||||
assert_eq!(payload["snapshot"]["bottomTab"], json!("adventure"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,19 @@ pub struct BeginStorySessionRequest {
|
||||
pub opening_summary: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BeginStoryRuntimeSessionRequest {
|
||||
pub world_type: String,
|
||||
#[serde(default)]
|
||||
pub custom_world_profile: Option<Value>,
|
||||
pub character: Value,
|
||||
#[serde(default)]
|
||||
pub runtime_mode: Option<String>,
|
||||
#[serde(default)]
|
||||
pub disable_persistence: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ContinueStoryRequest {
|
||||
@@ -20,6 +33,20 @@ pub struct ContinueStoryRequest {
|
||||
pub choice_function_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResolveStoryRuntimeActionRequest {
|
||||
pub story_session_id: String,
|
||||
#[serde(default)]
|
||||
pub client_version: Option<u32>,
|
||||
pub function_id: String,
|
||||
pub action_text: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub target_id: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub payload: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StorySessionPayload {
|
||||
@@ -65,6 +92,17 @@ pub struct StorySessionStateResponse {
|
||||
pub story_events: Vec<StoryEventPayload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoryRuntimeSnapshotPayload {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub saved_at: Option<String>,
|
||||
pub bottom_tab: String,
|
||||
pub game_state: Value,
|
||||
#[serde(default)]
|
||||
pub current_story: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoryRuntimeProjectionRequest {
|
||||
@@ -126,6 +164,7 @@ pub struct StoryRuntimeProjectionResponse {
|
||||
pub story_session: StorySessionPayload,
|
||||
pub story_events: Vec<StoryEventPayload>,
|
||||
pub server_version: u32,
|
||||
pub game_state: Value,
|
||||
pub actor: StoryRuntimeActorProjection,
|
||||
pub inventory: StoryRuntimeInventoryProjection,
|
||||
pub options: Vec<StoryRuntimeOptionProjection>,
|
||||
@@ -138,6 +177,12 @@ pub struct StoryRuntimeProjectionResponse {
|
||||
pub toast: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoryRuntimeMutationResponse {
|
||||
pub projection: StoryRuntimeProjectionResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StoryBattleRewardItemRequest {
|
||||
@@ -476,6 +521,11 @@ mod tests {
|
||||
created_at: "2.000000Z".to_string(),
|
||||
}],
|
||||
server_version: 2,
|
||||
game_state: json!({
|
||||
"runtimeSessionId": "runtime_1",
|
||||
"storySessionId": "storysess_1",
|
||||
"currentScene": "Story"
|
||||
}),
|
||||
actor: StoryRuntimeActorProjection {
|
||||
hp: 32,
|
||||
max_hp: 40,
|
||||
@@ -516,6 +566,7 @@ mod tests {
|
||||
json!("storysess_1")
|
||||
);
|
||||
assert_eq!(payload["serverVersion"], json!(2));
|
||||
assert_eq!(payload["gameState"]["storySessionId"], json!("storysess_1"));
|
||||
assert_eq!(payload["actor"]["maxHp"], json!(40));
|
||||
assert_eq!(
|
||||
payload["inventory"]["backpackItems"][0]["name"],
|
||||
@@ -527,6 +578,69 @@ mod tests {
|
||||
assert!(payload.get("presentation").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn story_runtime_mutation_response_wraps_projection_only() {
|
||||
let payload = serde_json::to_value(StoryRuntimeMutationResponse {
|
||||
projection: StoryRuntimeProjectionResponse {
|
||||
story_session: StorySessionPayload {
|
||||
story_session_id: "storysess_1".to_string(),
|
||||
runtime_session_id: "runtime_1".to_string(),
|
||||
actor_user_id: "user_1".to_string(),
|
||||
world_profile_id: "profile_1".to_string(),
|
||||
initial_prompt: "进入营地".to_string(),
|
||||
opening_summary: Some("营地开场".to_string()),
|
||||
latest_narrative_text: "营火还亮着。".to_string(),
|
||||
latest_choice_function_id: None,
|
||||
status: "active".to_string(),
|
||||
version: 1,
|
||||
created_at: "1.000000Z".to_string(),
|
||||
updated_at: "1.000000Z".to_string(),
|
||||
},
|
||||
story_events: Vec::new(),
|
||||
server_version: 1,
|
||||
game_state: json!({
|
||||
"runtimeSessionId": "runtime_1",
|
||||
"storySessionId": "storysess_1",
|
||||
"currentScene": "Story"
|
||||
}),
|
||||
actor: StoryRuntimeActorProjection {
|
||||
hp: 40,
|
||||
max_hp: 40,
|
||||
mana: 20,
|
||||
max_mana: 20,
|
||||
currency: 0,
|
||||
currency_text: "0 铜钱".to_string(),
|
||||
},
|
||||
inventory: StoryRuntimeInventoryProjection {
|
||||
backpack_items: Vec::new(),
|
||||
equipment_slots: Vec::new(),
|
||||
forge_recipes: Vec::new(),
|
||||
},
|
||||
options: Vec::new(),
|
||||
status: StoryRuntimeStatusProjection {
|
||||
in_battle: false,
|
||||
npc_interaction_active: false,
|
||||
current_encounter_id: None,
|
||||
current_npc_battle_mode: None,
|
||||
current_npc_battle_outcome: None,
|
||||
},
|
||||
current_narrative_text: Some("营火还亮着。".to_string()),
|
||||
action_result_text: None,
|
||||
toast: None,
|
||||
},
|
||||
})
|
||||
.expect("payload should serialize");
|
||||
|
||||
assert_eq!(
|
||||
payload["projection"]["storySession"]["storySessionId"],
|
||||
json!("storysess_1")
|
||||
);
|
||||
assert_eq!(payload["projection"]["serverVersion"], json!(1));
|
||||
assert!(payload.get("snapshot").is_none());
|
||||
assert!(payload.get("viewModel").is_none());
|
||||
assert!(payload.get("presentation").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn story_battle_responses_use_story_contract_shape() {
|
||||
let battle_state = StoryBattleStatePayload {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# spacetime-client 共享 package 说明
|
||||
|
||||
日期:`2026-04-20`
|
||||
日期:`2026-05-01`
|
||||
|
||||
## 1. package 职责
|
||||
|
||||
@@ -19,24 +19,26 @@
|
||||
|
||||
本轮方案见 [`SERVER_RS_DDD_WP_SC_SPACETIME_CLIENT_REFACTOR_2026-04-29.md`](../../../docs/technical/SERVER_RS_DDD_WP_SC_SPACETIME_CLIENT_REFACTOR_2026-04-29.md)。
|
||||
|
||||
## 2. 当前阶段说明
|
||||
## 2. 当前完成口径
|
||||
|
||||
当前目录已不再只是占位,当前阶段已经落下:
|
||||
当前目录已不再只是占位。`WP-SC Spacetime Client` 在当前稳定 SpacetimeDB facade 范围内已经完成收尾:
|
||||
|
||||
1. 通过 `npm run spacetime:generate -- --rust-only` 生成的公开 Rust bindings
|
||||
2. `DbConnection` 连接封装
|
||||
3. `confirm_asset_object_and_return` procedure 的最小调用适配
|
||||
4. `bind_asset_object_to_entity_and_return` procedure 的最小调用适配
|
||||
5. `api-server` 所需的 `asset_object` 确认与 `asset_entity_binding` 绑定返回值转换
|
||||
1. 通过 `npm run spacetime:generate -- --rust-only` 生成并纳管公开 Rust bindings。
|
||||
2. `DbConnection` 连接池、握手等待、超时和断线清理已封装在 `SpacetimeClient` 内部。
|
||||
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 副本。
|
||||
|
||||
`confirm_asset_object_and_return` 与 `bind_asset_object_to_entity_and_return` 的调用必须等到 SDK `on_connect` 回调后再发起。`DbConnection::build()` 只代表 WebSocket 已经初始化,不代表 SpacetimeDB 身份握手完成;如果过早调用 procedure,本地联调会表现为连接建立但请求长期没有回调,最终等到 idle timeout。
|
||||
|
||||
后续与本 package 直接相关的任务包括:
|
||||
后续新增工作只随 `WP-ST` 新 table / reducer / procedure 或 row shape 稳定后按领域增量接入,不再把整个 `WP-SC` 包保持为进行中状态。新增 facade 时必须继续满足:
|
||||
|
||||
1. 固化 bindings 生成与更新脚本
|
||||
2. 设计 reducer、procedure、view、订阅的统一调用接口
|
||||
3. 设计身份透传与连接配置策略
|
||||
4. 设计 Axum / worker / 测试环境下的客户端复用方式
|
||||
1. 不手写 `module_bindings` 生成物。
|
||||
2. 不在 `spacetime-client` 内新增领域规则。
|
||||
3. procedure / reducer shape 稳定后再接 typed facade。
|
||||
4. 错误映射继续使用 `SpacetimeClientError` helper。
|
||||
5. mapper 测试或 facade 定向测试随新增场景补齐。
|
||||
|
||||
## 2.1 `module_bindings` 生成物约束
|
||||
|
||||
@@ -44,19 +46,19 @@
|
||||
|
||||
1. 只允许通过仓库根目录 `npm run spacetime:generate -- --rust-only` 刷新,不允许手工修改。
|
||||
2. 不生成私有表绑定,不追加 `--include-private`;如后端需要读取私有表,应先在 `api-server` 或模块层补明确 contract,而不是让客户端 crate 直接依赖私有表结构。
|
||||
3. 不允许对该目录额外执行 `rustfmt`,生成物格式只接受 SpacetimeDB CLI 生成阶段自身输出。
|
||||
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 下直接把 Rust bindings 输出到本目录时,SpacetimeDB CLI `2.1.0` 的生成后 formatter 可能因为路径参数总长触发 `文件名或扩展名太长`;仓库脚本会先输出到短临时目录,再同步回本目录。
|
||||
5. Windows 下 SpacetimeDB CLI `2.1.0` 的生成后 formatter 可能因为一次性传入过多 Rust 文件路径而失败;仓库脚本会先输出到短临时目录,必要时接管分批格式化,再同步回本目录。
|
||||
|
||||
### 2.1.1 绑定缺文件恢复流程
|
||||
|
||||
若 `mod.rs` 已声明 `*_table` 模块,但目录内缺少对应 `*_table.rs` 文件,说明 Rust bindings 刷新不完整。不要手工补 generated code,统一在仓库根目录执行:
|
||||
|
||||
```powershell
|
||||
spacetime generate --no-config --lang rust --include-private --out-dir .\server-rs\crates\spacetime-client\src\module_bindings --module-path .\server-rs\crates\spacetime-module --yes
|
||||
npm.cmd run spacetime:generate -- --rust-only
|
||||
```
|
||||
|
||||
这里必须带 `--no-config`:仓库根目录的 `spacetime.json` 同时配置了 TypeScript 与 Rust 两个生成目标,直接追加 `--lang` / `--out-dir` 会触发 SpacetimeDB CLI 的多目标参数冲突。
|
||||
脚本内部会使用 `--no-config`:仓库根目录的 `spacetime.json` 同时配置了 TypeScript 与 Rust 两个生成目标,直接追加 `--lang` / `--out-dir` 会触发 SpacetimeDB CLI 的多目标参数冲突。
|
||||
|
||||
生成后用以下命令确认 `mod.rs` 声明的模块都有落盘文件:
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ impl SpacetimeClient {
|
||||
.start_ai_task_then(reducer_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(|inner| inner.map_err(SpacetimeClientError::Runtime));
|
||||
.and_then(|inner| inner.map_err(SpacetimeClientError::reducer_failed));
|
||||
send_reducer_once(&callback_sender, mapped);
|
||||
})
|
||||
{
|
||||
@@ -60,7 +60,7 @@ impl SpacetimeClient {
|
||||
.start_ai_task_stage_then(reducer_input, move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(|inner| inner.map_err(SpacetimeClientError::Runtime));
|
||||
.and_then(|inner| inner.map_err(SpacetimeClientError::reducer_failed));
|
||||
send_reducer_once(&callback_sender, mapped);
|
||||
})
|
||||
{
|
||||
|
||||
@@ -6,8 +6,7 @@ impl SpacetimeClient {
|
||||
&self,
|
||||
input: DomainBattleStateInput,
|
||||
) -> Result<BattleStateRecord, SpacetimeClientError> {
|
||||
validate_battle_state_input(&input)
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?;
|
||||
validate_battle_state_input(&input).map_err(SpacetimeClientError::validation_failed)?;
|
||||
let procedure_input = input.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
@@ -29,7 +28,7 @@ impl SpacetimeClient {
|
||||
battle_state_id: String,
|
||||
) -> Result<BattleStateRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_battle_state_query_input(battle_state_id)
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
@@ -50,7 +49,7 @@ impl SpacetimeClient {
|
||||
input: DomainResolveCombatActionInput,
|
||||
) -> Result<ResolveCombatActionRecord, SpacetimeClientError> {
|
||||
validate_resolve_combat_action_input(&input)
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?;
|
||||
.map_err(SpacetimeClientError::validation_failed)?;
|
||||
let procedure_input = input.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
|
||||
@@ -8,7 +8,7 @@ impl SpacetimeClient {
|
||||
) -> Result<RuntimeInventoryStateRecord, SpacetimeClientError> {
|
||||
let procedure_input =
|
||||
build_runtime_inventory_state_query_input(runtime_session_id, actor_user_id)
|
||||
.map_err(|error| SpacetimeClientError::Runtime(error.to_string()))?
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
self.call_after_connect(move |connection, sender| {
|
||||
|
||||
@@ -432,6 +432,14 @@ impl SpacetimeClientError {
|
||||
Self::Procedure(error.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn validation_failed(error: impl fmt::Display) -> Self {
|
||||
Self::Runtime(error.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn reducer_failed(message: String) -> Self {
|
||||
Self::Runtime(message)
|
||||
}
|
||||
|
||||
pub(crate) fn procedure_failed(message: Option<String>) -> Self {
|
||||
Self::Procedure(message.unwrap_or_else(|| "SpacetimeDB procedure 返回未知错误".to_string()))
|
||||
}
|
||||
@@ -515,3 +523,40 @@ impl fmt::Display for SpacetimeClientError {
|
||||
}
|
||||
|
||||
impl Error for SpacetimeClientError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn procedure_failed_keeps_server_message_or_default() {
|
||||
assert_eq!(
|
||||
SpacetimeClientError::procedure_failed(Some("领域错误".to_string())).to_string(),
|
||||
"领域错误"
|
||||
);
|
||||
assert_eq!(
|
||||
SpacetimeClientError::procedure_failed(None).to_string(),
|
||||
"SpacetimeDB procedure 返回未知错误"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_snapshot_names_adapter_boundary() {
|
||||
assert_eq!(
|
||||
SpacetimeClientError::missing_snapshot("story session 快照").to_string(),
|
||||
"SpacetimeDB procedure 未返回story session 快照"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_and_reducer_failures_stay_runtime_classified() {
|
||||
assert!(matches!(
|
||||
SpacetimeClientError::validation_failed("字段缺失"),
|
||||
SpacetimeClientError::Runtime(_)
|
||||
));
|
||||
assert!(matches!(
|
||||
SpacetimeClientError::reducer_failed("状态非法".to_string()),
|
||||
SpacetimeClientError::Runtime(_)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::quest_record_input_type::QuestRecordInput;
|
||||
|
||||
@@ -19,10 +14,8 @@ pub(super) struct AcceptQuestArgs {
|
||||
|
||||
impl From<AcceptQuestArgs> for super::Reducer {
|
||||
fn from(args: AcceptQuestArgs) -> Self {
|
||||
Self::AcceptQuest {
|
||||
input: args.input,
|
||||
}
|
||||
}
|
||||
Self::AcceptQuest { input: args.input }
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AcceptQuestArgs {
|
||||
@@ -40,9 +33,8 @@ pub trait accept_quest {
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`accept_quest:accept_quest_then`] to run a callback after the reducer completes.
|
||||
fn accept_quest(&self, input: QuestRecordInput,
|
||||
) -> __sdk::Result<()> {
|
||||
self.accept_quest_then(input, |_, _| {})
|
||||
fn accept_quest(&self, input: QuestRecordInput) -> __sdk::Result<()> {
|
||||
self.accept_quest_then(input, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `accept_quest` to run as soon as possible,
|
||||
@@ -55,9 +47,11 @@ pub trait accept_quest {
|
||||
&self,
|
||||
input: QuestRecordInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -66,11 +60,13 @@ impl accept_quest for super::RemoteReducers {
|
||||
&self,
|
||||
input: QuestRecordInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp.invoke_reducer_with_callback(AcceptQuestArgs { input, }, callback)
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(AcceptQuestArgs { input }, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::quest_completion_ack_input_type::QuestCompletionAckInput;
|
||||
|
||||
@@ -19,10 +14,8 @@ pub(super) struct AcknowledgeQuestCompletionArgs {
|
||||
|
||||
impl From<AcknowledgeQuestCompletionArgs> for super::Reducer {
|
||||
fn from(args: AcknowledgeQuestCompletionArgs) -> Self {
|
||||
Self::AcknowledgeQuestCompletion {
|
||||
input: args.input,
|
||||
}
|
||||
}
|
||||
Self::AcknowledgeQuestCompletion { input: args.input }
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AcknowledgeQuestCompletionArgs {
|
||||
@@ -40,9 +33,8 @@ pub trait acknowledge_quest_completion {
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`acknowledge_quest_completion:acknowledge_quest_completion_then`] to run a callback after the reducer completes.
|
||||
fn acknowledge_quest_completion(&self, input: QuestCompletionAckInput,
|
||||
) -> __sdk::Result<()> {
|
||||
self.acknowledge_quest_completion_then(input, |_, _| {})
|
||||
fn acknowledge_quest_completion(&self, input: QuestCompletionAckInput) -> __sdk::Result<()> {
|
||||
self.acknowledge_quest_completion_then(input, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `acknowledge_quest_completion` to run as soon as possible,
|
||||
@@ -55,9 +47,11 @@ pub trait acknowledge_quest_completion {
|
||||
&self,
|
||||
input: QuestCompletionAckInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -66,11 +60,13 @@ impl acknowledge_quest_completion for super::RemoteReducers {
|
||||
&self,
|
||||
input: QuestCompletionAckInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp.invoke_reducer_with_callback(AcknowledgeQuestCompletionArgs { input, }, callback)
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(AcknowledgeQuestCompletionArgs { input }, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::runtime_profile_redeem_code_admin_disable_input_type::RuntimeProfileRedeemCodeAdminDisableInput;
|
||||
use super::runtime_profile_redeem_code_admin_procedure_result_type::RuntimeProfileRedeemCodeAdminProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminDisableProfileRedeemCodeArgs {
|
||||
struct AdminDisableProfileRedeemCodeArgs {
|
||||
pub input: RuntimeProfileRedeemCodeAdminDisableInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AdminDisableProfileRedeemCodeArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,19 @@ impl __sdk::InModule for AdminDisableProfileRedeemCodeArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_disable_profile_redeem_code {
|
||||
fn admin_disable_profile_redeem_code(&self, input: RuntimeProfileRedeemCodeAdminDisableInput,
|
||||
) {
|
||||
self.admin_disable_profile_redeem_code_then(input, |_, _| {});
|
||||
fn admin_disable_profile_redeem_code(&self, input: RuntimeProfileRedeemCodeAdminDisableInput) {
|
||||
self.admin_disable_profile_redeem_code_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_disable_profile_redeem_code_then(
|
||||
&self,
|
||||
input: RuntimeProfileRedeemCodeAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +43,17 @@ impl admin_disable_profile_redeem_code for super::RemoteProcedures {
|
||||
&self,
|
||||
input: RuntimeProfileRedeemCodeAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>(
|
||||
"admin_disable_profile_redeem_code",
|
||||
AdminDisableProfileRedeemCodeArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>(
|
||||
"admin_disable_profile_redeem_code",
|
||||
AdminDisableProfileRedeemCodeArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::runtime_profile_redeem_code_admin_procedure_result_type::RuntimeProfileRedeemCodeAdminProcedureResult;
|
||||
use super::runtime_profile_redeem_code_admin_upsert_input_type::RuntimeProfileRedeemCodeAdminUpsertInput;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdminUpsertProfileRedeemCodeArgs {
|
||||
struct AdminUpsertProfileRedeemCodeArgs {
|
||||
pub input: RuntimeProfileRedeemCodeAdminUpsertInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AdminUpsertProfileRedeemCodeArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,19 @@ impl __sdk::InModule for AdminUpsertProfileRedeemCodeArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait admin_upsert_profile_redeem_code {
|
||||
fn admin_upsert_profile_redeem_code(&self, input: RuntimeProfileRedeemCodeAdminUpsertInput,
|
||||
) {
|
||||
self.admin_upsert_profile_redeem_code_then(input, |_, _| {});
|
||||
fn admin_upsert_profile_redeem_code(&self, input: RuntimeProfileRedeemCodeAdminUpsertInput) {
|
||||
self.admin_upsert_profile_redeem_code_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn admin_upsert_profile_redeem_code_then(
|
||||
&self,
|
||||
input: RuntimeProfileRedeemCodeAdminUpsertInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +43,17 @@ impl admin_upsert_profile_redeem_code for super::RemoteProcedures {
|
||||
&self,
|
||||
input: RuntimeProfileRedeemCodeAdminUpsertInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>(
|
||||
"admin_upsert_profile_redeem_code",
|
||||
AdminUpsertProfileRedeemCodeArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>(
|
||||
"admin_upsert_profile_redeem_code",
|
||||
AdminUpsertProfileRedeemCodeArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::puzzle_run_next_level_input_type::PuzzleRunNextLevelInput;
|
||||
use super::puzzle_run_procedure_result_type::PuzzleRunProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AdvancePuzzleNextLevelArgs {
|
||||
struct AdvancePuzzleNextLevelArgs {
|
||||
pub input: PuzzleRunNextLevelInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AdvancePuzzleNextLevelArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,19 @@ impl __sdk::InModule for AdvancePuzzleNextLevelArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait advance_puzzle_next_level {
|
||||
fn advance_puzzle_next_level(&self, input: PuzzleRunNextLevelInput,
|
||||
) {
|
||||
self.advance_puzzle_next_level_then(input, |_, _| {});
|
||||
fn advance_puzzle_next_level(&self, input: PuzzleRunNextLevelInput) {
|
||||
self.advance_puzzle_next_level_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn advance_puzzle_next_level_then(
|
||||
&self,
|
||||
input: PuzzleRunNextLevelInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<PuzzleRunProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<PuzzleRunProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +43,17 @@ impl advance_puzzle_next_level for super::RemoteProcedures {
|
||||
&self,
|
||||
input: PuzzleRunNextLevelInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<PuzzleRunProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<PuzzleRunProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>(
|
||||
"advance_puzzle_next_level",
|
||||
AdvancePuzzleNextLevelArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, PuzzleRunProcedureResult>(
|
||||
"advance_puzzle_next_level",
|
||||
AdvancePuzzleNextLevelArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_result_reference_kind_type::AiResultReferenceKind;
|
||||
|
||||
@@ -17,12 +12,10 @@ pub struct AiResultReferenceInput {
|
||||
pub task_id: String,
|
||||
pub reference_kind: AiResultReferenceKind,
|
||||
pub reference_id: String,
|
||||
pub label: Option::<String>,
|
||||
pub label: Option<String>,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiResultReferenceInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -24,12 +19,8 @@ pub enum AiResultReferenceKind {
|
||||
RuntimeItemRecord,
|
||||
|
||||
AssetObject,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AiResultReferenceKind {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_result_reference_kind_type::AiResultReferenceKind;
|
||||
|
||||
@@ -18,12 +13,10 @@ pub struct AiResultReferenceSnapshot {
|
||||
pub task_id: String,
|
||||
pub reference_kind: AiResultReferenceKind,
|
||||
pub reference_id: String,
|
||||
pub label: Option::<String>,
|
||||
pub label: Option<String>,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiResultReferenceSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_result_reference_kind_type::AiResultReferenceKind;
|
||||
|
||||
@@ -19,16 +14,14 @@ pub struct AiResultReference {
|
||||
pub task_id: String,
|
||||
pub reference_kind: AiResultReferenceKind,
|
||||
pub reference_id: String,
|
||||
pub label: Option::<String>,
|
||||
pub label: Option<String>,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiResultReference {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AiResultReference`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -38,7 +31,7 @@ pub struct AiResultReferenceCols {
|
||||
pub task_id: __sdk::__query_builder::Col<AiResultReference, String>,
|
||||
pub reference_kind: __sdk::__query_builder::Col<AiResultReference, AiResultReferenceKind>,
|
||||
pub reference_id: __sdk::__query_builder::Col<AiResultReference, String>,
|
||||
pub label: __sdk::__query_builder::Col<AiResultReference, Option::<String>>,
|
||||
pub label: __sdk::__query_builder::Col<AiResultReference, Option<String>>,
|
||||
pub created_at: __sdk::__query_builder::Col<AiResultReference, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
@@ -46,14 +39,16 @@ impl __sdk::__query_builder::HasCols for AiResultReference {
|
||||
type Cols = AiResultReferenceCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
AiResultReferenceCols {
|
||||
result_reference_row_id: __sdk::__query_builder::Col::new(table_name, "result_reference_row_id"),
|
||||
result_reference_row_id: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"result_reference_row_id",
|
||||
),
|
||||
result_ref_id: __sdk::__query_builder::Col::new(table_name, "result_ref_id"),
|
||||
task_id: __sdk::__query_builder::Col::new(table_name, "task_id"),
|
||||
reference_kind: __sdk::__query_builder::Col::new(table_name, "reference_kind"),
|
||||
reference_id: __sdk::__query_builder::Col::new(table_name, "reference_id"),
|
||||
label: __sdk::__query_builder::Col::new(table_name, "label"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,12 +65,13 @@ impl __sdk::__query_builder::HasIxCols for AiResultReference {
|
||||
type IxCols = AiResultReferenceIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
AiResultReferenceIxCols {
|
||||
result_reference_row_id: __sdk::__query_builder::IxCol::new(table_name, "result_reference_row_id"),
|
||||
result_reference_row_id: __sdk::__query_builder::IxCol::new(
|
||||
table_name,
|
||||
"result_reference_row_id",
|
||||
),
|
||||
task_id: __sdk::__query_builder::IxCol::new(table_name, "task_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AiResultReference {}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
|
||||
@@ -16,14 +11,12 @@ use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
pub struct AiStageCompletionInput {
|
||||
pub task_id: String,
|
||||
pub stage_kind: AiTaskStageKind,
|
||||
pub text_output: Option::<String>,
|
||||
pub structured_payload_json: Option::<String>,
|
||||
pub warning_messages: Vec::<String>,
|
||||
pub text_output: Option<String>,
|
||||
pub structured_payload_json: Option<String>,
|
||||
pub warning_messages: Vec<String>,
|
||||
pub completed_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiStageCompletionInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -17,8 +11,6 @@ pub struct AiTaskCancelInput {
|
||||
pub completed_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskCancelInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_kind_type::AiTaskKind;
|
||||
use super::ai_task_stage_blueprint_type::AiTaskStageBlueprint;
|
||||
@@ -20,14 +15,12 @@ pub struct AiTaskCreateInput {
|
||||
pub owner_user_id: String,
|
||||
pub request_label: String,
|
||||
pub source_module: String,
|
||||
pub source_entity_id: Option::<String>,
|
||||
pub request_payload_json: Option::<String>,
|
||||
pub stages: Vec::<AiTaskStageBlueprint>,
|
||||
pub source_entity_id: Option<String>,
|
||||
pub request_payload_json: Option<String>,
|
||||
pub stages: Vec<AiTaskStageBlueprint>,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskCreateInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -24,12 +19,8 @@ pub enum AiTaskEventKind {
|
||||
TextChunkAppended,
|
||||
|
||||
ResultReferenceAttached,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskEventKind {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use super::ai_task_event_type::AiTaskEvent;
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
use super::ai_task_event_kind_type::AiTaskEventKind;
|
||||
use super::ai_task_event_type::AiTaskEvent;
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `ai_task_event`.
|
||||
///
|
||||
@@ -51,8 +46,12 @@ impl<'ctx> __sdk::EventTable for AiTaskEventTableHandle<'ctx> {
|
||||
type Row = AiTaskEvent;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 { self.imp.count() }
|
||||
fn iter(&self) -> impl Iterator<Item = AiTaskEvent> + '_ { self.imp.iter() }
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = AiTaskEvent> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = AiTaskEventInsertCallbackId;
|
||||
|
||||
@@ -70,8 +69,7 @@ impl<'ctx> __sdk::EventTable for AiTaskEventTableHandle<'ctx> {
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
|
||||
let _table = client_cache.get_or_make_table::<AiTaskEvent>("ai_task_event");
|
||||
let _table = client_cache.get_or_make_table::<AiTaskEvent>("ai_task_event");
|
||||
_table.add_unique_constraint::<String>("event_id", |row| &row.event_id);
|
||||
}
|
||||
|
||||
@@ -80,26 +78,24 @@ pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<AiTaskEvent>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse(
|
||||
"TableUpdate<AiTaskEvent>",
|
||||
"TableUpdate",
|
||||
).with_cause(e).into()
|
||||
__sdk::InternalError::failed_parse("TableUpdate<AiTaskEvent>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `AiTaskEvent`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait ai_task_eventQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `AiTaskEvent`.
|
||||
fn ai_task_event(&self) -> __sdk::__query_builder::Table<AiTaskEvent>;
|
||||
}
|
||||
|
||||
impl ai_task_eventQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn ai_task_event(&self) -> __sdk::__query_builder::Table<AiTaskEvent> {
|
||||
__sdk::__query_builder::Table::new("ai_task_event")
|
||||
}
|
||||
}
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `AiTaskEvent`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait ai_task_eventQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `AiTaskEvent`.
|
||||
fn ai_task_event(&self) -> __sdk::__query_builder::Table<AiTaskEvent>;
|
||||
}
|
||||
|
||||
impl ai_task_eventQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn ai_task_event(&self) -> __sdk::__query_builder::Table<AiTaskEvent> {
|
||||
__sdk::__query_builder::Table::new("ai_task_event")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
use super::ai_task_event_kind_type::AiTaskEventKind;
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -20,19 +15,17 @@ pub struct AiTaskEvent {
|
||||
pub task_id: String,
|
||||
pub owner_user_id: String,
|
||||
pub event_kind: AiTaskEventKind,
|
||||
pub task_status: Option::<AiTaskStatus>,
|
||||
pub stage_kind: Option::<AiTaskStageKind>,
|
||||
pub text_chunk_row_id: Option::<String>,
|
||||
pub result_reference_row_id: Option::<String>,
|
||||
pub task_status: Option<AiTaskStatus>,
|
||||
pub stage_kind: Option<AiTaskStageKind>,
|
||||
pub text_chunk_row_id: Option<String>,
|
||||
pub result_reference_row_id: Option<String>,
|
||||
pub occurred_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskEvent {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AiTaskEvent`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -41,10 +34,10 @@ pub struct AiTaskEventCols {
|
||||
pub task_id: __sdk::__query_builder::Col<AiTaskEvent, String>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AiTaskEvent, String>,
|
||||
pub event_kind: __sdk::__query_builder::Col<AiTaskEvent, AiTaskEventKind>,
|
||||
pub task_status: __sdk::__query_builder::Col<AiTaskEvent, Option::<AiTaskStatus>>,
|
||||
pub stage_kind: __sdk::__query_builder::Col<AiTaskEvent, Option::<AiTaskStageKind>>,
|
||||
pub text_chunk_row_id: __sdk::__query_builder::Col<AiTaskEvent, Option::<String>>,
|
||||
pub result_reference_row_id: __sdk::__query_builder::Col<AiTaskEvent, Option::<String>>,
|
||||
pub task_status: __sdk::__query_builder::Col<AiTaskEvent, Option<AiTaskStatus>>,
|
||||
pub stage_kind: __sdk::__query_builder::Col<AiTaskEvent, Option<AiTaskStageKind>>,
|
||||
pub text_chunk_row_id: __sdk::__query_builder::Col<AiTaskEvent, Option<String>>,
|
||||
pub result_reference_row_id: __sdk::__query_builder::Col<AiTaskEvent, Option<String>>,
|
||||
pub occurred_at: __sdk::__query_builder::Col<AiTaskEvent, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
@@ -59,9 +52,11 @@ impl __sdk::__query_builder::HasCols for AiTaskEvent {
|
||||
task_status: __sdk::__query_builder::Col::new(table_name, "task_status"),
|
||||
stage_kind: __sdk::__query_builder::Col::new(table_name, "stage_kind"),
|
||||
text_chunk_row_id: __sdk::__query_builder::Col::new(table_name, "text_chunk_row_id"),
|
||||
result_reference_row_id: __sdk::__query_builder::Col::new(table_name, "result_reference_row_id"),
|
||||
result_reference_row_id: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"result_reference_row_id",
|
||||
),
|
||||
occurred_at: __sdk::__query_builder::Col::new(table_name, "occurred_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,8 +77,6 @@ impl __sdk::__query_builder::HasIxCols for AiTaskEvent {
|
||||
event_id: __sdk::__query_builder::IxCol::new(table_name, "event_id"),
|
||||
owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"),
|
||||
task_id: __sdk::__query_builder::IxCol::new(table_name, "task_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -18,8 +12,6 @@ pub struct AiTaskFailureInput {
|
||||
pub completed_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskFailureInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -17,8 +11,6 @@ pub struct AiTaskFinishInput {
|
||||
pub completed_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskFinishInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -24,12 +19,8 @@ pub enum AiTaskKind {
|
||||
QuestIntent,
|
||||
|
||||
RuntimeItemIntent,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskKind {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_snapshot_type::AiTaskSnapshot;
|
||||
use super::ai_text_chunk_snapshot_type::AiTextChunkSnapshot;
|
||||
@@ -16,13 +11,11 @@ use super::ai_text_chunk_snapshot_type::AiTextChunkSnapshot;
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AiTaskProcedureResult {
|
||||
pub ok: bool,
|
||||
pub task: Option::<AiTaskSnapshot>,
|
||||
pub text_chunk: Option::<AiTextChunkSnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
pub task: Option<AiTaskSnapshot>,
|
||||
pub text_chunk: Option<AiTextChunkSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,12 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_kind_type::AiTaskKind;
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
use super::ai_task_stage_snapshot_type::AiTaskStageSnapshot;
|
||||
use super::ai_result_reference_snapshot_type::AiResultReferenceSnapshot;
|
||||
use super::ai_task_kind_type::AiTaskKind;
|
||||
use super::ai_task_stage_snapshot_type::AiTaskStageSnapshot;
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -22,23 +17,21 @@ pub struct AiTaskSnapshot {
|
||||
pub owner_user_id: String,
|
||||
pub request_label: String,
|
||||
pub source_module: String,
|
||||
pub source_entity_id: Option::<String>,
|
||||
pub request_payload_json: Option::<String>,
|
||||
pub source_entity_id: Option<String>,
|
||||
pub request_payload_json: Option<String>,
|
||||
pub status: AiTaskStatus,
|
||||
pub failure_message: Option::<String>,
|
||||
pub stages: Vec::<AiTaskStageSnapshot>,
|
||||
pub result_references: Vec::<AiResultReferenceSnapshot>,
|
||||
pub latest_text_output: Option::<String>,
|
||||
pub latest_structured_payload_json: Option::<String>,
|
||||
pub failure_message: Option<String>,
|
||||
pub stages: Vec<AiTaskStageSnapshot>,
|
||||
pub result_references: Vec<AiResultReferenceSnapshot>,
|
||||
pub latest_text_output: Option<String>,
|
||||
pub latest_structured_payload_json: Option<String>,
|
||||
pub version: u32,
|
||||
pub created_at_micros: i64,
|
||||
pub started_at_micros: Option::<i64>,
|
||||
pub completed_at_micros: Option::<i64>,
|
||||
pub started_at_micros: Option<i64>,
|
||||
pub completed_at_micros: Option<i64>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
|
||||
@@ -20,8 +15,6 @@ pub struct AiTaskStageBlueprint {
|
||||
pub order: u32,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStageBlueprint {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -22,12 +17,8 @@ pub enum AiTaskStageKind {
|
||||
NormalizeResult,
|
||||
|
||||
PersistResult,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStageKind {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
use super::ai_task_stage_status_type::AiTaskStageStatus;
|
||||
@@ -20,15 +15,13 @@ pub struct AiTaskStageSnapshot {
|
||||
pub detail: String,
|
||||
pub order: u32,
|
||||
pub status: AiTaskStageStatus,
|
||||
pub text_output: Option::<String>,
|
||||
pub structured_payload_json: Option::<String>,
|
||||
pub warning_messages: Vec::<String>,
|
||||
pub started_at_micros: Option::<i64>,
|
||||
pub completed_at_micros: Option::<i64>,
|
||||
pub text_output: Option<String>,
|
||||
pub structured_payload_json: Option<String>,
|
||||
pub warning_messages: Vec<String>,
|
||||
pub started_at_micros: Option<i64>,
|
||||
pub completed_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStageSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
|
||||
@@ -19,8 +14,6 @@ pub struct AiTaskStageStartInput {
|
||||
pub started_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStageStartInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -20,12 +15,8 @@ pub enum AiTaskStageStatus {
|
||||
Completed,
|
||||
|
||||
Skipped,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStageStatus {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
use super::ai_task_stage_status_type::AiTaskStageStatus;
|
||||
@@ -22,19 +17,17 @@ pub struct AiTaskStage {
|
||||
pub detail: String,
|
||||
pub stage_order: u32,
|
||||
pub status: AiTaskStageStatus,
|
||||
pub text_output: Option::<String>,
|
||||
pub structured_payload_json: Option::<String>,
|
||||
pub warning_messages: Vec::<String>,
|
||||
pub started_at: Option::<__sdk::Timestamp>,
|
||||
pub completed_at: Option::<__sdk::Timestamp>,
|
||||
pub text_output: Option<String>,
|
||||
pub structured_payload_json: Option<String>,
|
||||
pub warning_messages: Vec<String>,
|
||||
pub started_at: Option<__sdk::Timestamp>,
|
||||
pub completed_at: Option<__sdk::Timestamp>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStage {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AiTaskStage`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -46,11 +39,11 @@ pub struct AiTaskStageCols {
|
||||
pub detail: __sdk::__query_builder::Col<AiTaskStage, String>,
|
||||
pub stage_order: __sdk::__query_builder::Col<AiTaskStage, u32>,
|
||||
pub status: __sdk::__query_builder::Col<AiTaskStage, AiTaskStageStatus>,
|
||||
pub text_output: __sdk::__query_builder::Col<AiTaskStage, Option::<String>>,
|
||||
pub structured_payload_json: __sdk::__query_builder::Col<AiTaskStage, Option::<String>>,
|
||||
pub warning_messages: __sdk::__query_builder::Col<AiTaskStage, Vec::<String>>,
|
||||
pub started_at: __sdk::__query_builder::Col<AiTaskStage, Option::<__sdk::Timestamp>>,
|
||||
pub completed_at: __sdk::__query_builder::Col<AiTaskStage, Option::<__sdk::Timestamp>>,
|
||||
pub text_output: __sdk::__query_builder::Col<AiTaskStage, Option<String>>,
|
||||
pub structured_payload_json: __sdk::__query_builder::Col<AiTaskStage, Option<String>>,
|
||||
pub warning_messages: __sdk::__query_builder::Col<AiTaskStage, Vec<String>>,
|
||||
pub started_at: __sdk::__query_builder::Col<AiTaskStage, Option<__sdk::Timestamp>>,
|
||||
pub completed_at: __sdk::__query_builder::Col<AiTaskStage, Option<__sdk::Timestamp>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for AiTaskStage {
|
||||
@@ -65,11 +58,13 @@ impl __sdk::__query_builder::HasCols for AiTaskStage {
|
||||
stage_order: __sdk::__query_builder::Col::new(table_name, "stage_order"),
|
||||
status: __sdk::__query_builder::Col::new(table_name, "status"),
|
||||
text_output: __sdk::__query_builder::Col::new(table_name, "text_output"),
|
||||
structured_payload_json: __sdk::__query_builder::Col::new(table_name, "structured_payload_json"),
|
||||
structured_payload_json: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"structured_payload_json",
|
||||
),
|
||||
warning_messages: __sdk::__query_builder::Col::new(table_name, "warning_messages"),
|
||||
started_at: __sdk::__query_builder::Col::new(table_name, "started_at"),
|
||||
completed_at: __sdk::__query_builder::Col::new(table_name, "completed_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,10 +83,8 @@ impl __sdk::__query_builder::HasIxCols for AiTaskStage {
|
||||
AiTaskStageIxCols {
|
||||
task_id: __sdk::__query_builder::IxCol::new(table_name, "task_id"),
|
||||
task_stage_id: __sdk::__query_builder::IxCol::new(table_name, "task_stage_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AiTaskStage {}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -17,8 +11,6 @@ pub struct AiTaskStartInput {
|
||||
pub started_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStartInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -22,12 +17,8 @@ pub enum AiTaskStatus {
|
||||
Failed,
|
||||
|
||||
Cancelled,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTaskStatus {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_kind_type::AiTaskKind;
|
||||
use super::ai_task_status_type::AiTaskStatus;
|
||||
@@ -20,25 +15,23 @@ pub struct AiTask {
|
||||
pub owner_user_id: String,
|
||||
pub request_label: String,
|
||||
pub source_module: String,
|
||||
pub source_entity_id: Option::<String>,
|
||||
pub request_payload_json: Option::<String>,
|
||||
pub source_entity_id: Option<String>,
|
||||
pub request_payload_json: Option<String>,
|
||||
pub status: AiTaskStatus,
|
||||
pub failure_message: Option::<String>,
|
||||
pub latest_text_output: Option::<String>,
|
||||
pub latest_structured_payload_json: Option::<String>,
|
||||
pub failure_message: Option<String>,
|
||||
pub latest_text_output: Option<String>,
|
||||
pub latest_structured_payload_json: Option<String>,
|
||||
pub version: u32,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
pub started_at: Option::<__sdk::Timestamp>,
|
||||
pub completed_at: Option::<__sdk::Timestamp>,
|
||||
pub started_at: Option<__sdk::Timestamp>,
|
||||
pub completed_at: Option<__sdk::Timestamp>,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTask {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AiTask`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -48,16 +41,16 @@ pub struct AiTaskCols {
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AiTask, String>,
|
||||
pub request_label: __sdk::__query_builder::Col<AiTask, String>,
|
||||
pub source_module: __sdk::__query_builder::Col<AiTask, String>,
|
||||
pub source_entity_id: __sdk::__query_builder::Col<AiTask, Option::<String>>,
|
||||
pub request_payload_json: __sdk::__query_builder::Col<AiTask, Option::<String>>,
|
||||
pub source_entity_id: __sdk::__query_builder::Col<AiTask, Option<String>>,
|
||||
pub request_payload_json: __sdk::__query_builder::Col<AiTask, Option<String>>,
|
||||
pub status: __sdk::__query_builder::Col<AiTask, AiTaskStatus>,
|
||||
pub failure_message: __sdk::__query_builder::Col<AiTask, Option::<String>>,
|
||||
pub latest_text_output: __sdk::__query_builder::Col<AiTask, Option::<String>>,
|
||||
pub latest_structured_payload_json: __sdk::__query_builder::Col<AiTask, Option::<String>>,
|
||||
pub failure_message: __sdk::__query_builder::Col<AiTask, Option<String>>,
|
||||
pub latest_text_output: __sdk::__query_builder::Col<AiTask, Option<String>>,
|
||||
pub latest_structured_payload_json: __sdk::__query_builder::Col<AiTask, Option<String>>,
|
||||
pub version: __sdk::__query_builder::Col<AiTask, u32>,
|
||||
pub created_at: __sdk::__query_builder::Col<AiTask, __sdk::Timestamp>,
|
||||
pub started_at: __sdk::__query_builder::Col<AiTask, Option::<__sdk::Timestamp>>,
|
||||
pub completed_at: __sdk::__query_builder::Col<AiTask, Option::<__sdk::Timestamp>>,
|
||||
pub started_at: __sdk::__query_builder::Col<AiTask, Option<__sdk::Timestamp>>,
|
||||
pub completed_at: __sdk::__query_builder::Col<AiTask, Option<__sdk::Timestamp>>,
|
||||
pub updated_at: __sdk::__query_builder::Col<AiTask, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
@@ -71,17 +64,22 @@ impl __sdk::__query_builder::HasCols for AiTask {
|
||||
request_label: __sdk::__query_builder::Col::new(table_name, "request_label"),
|
||||
source_module: __sdk::__query_builder::Col::new(table_name, "source_module"),
|
||||
source_entity_id: __sdk::__query_builder::Col::new(table_name, "source_entity_id"),
|
||||
request_payload_json: __sdk::__query_builder::Col::new(table_name, "request_payload_json"),
|
||||
request_payload_json: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"request_payload_json",
|
||||
),
|
||||
status: __sdk::__query_builder::Col::new(table_name, "status"),
|
||||
failure_message: __sdk::__query_builder::Col::new(table_name, "failure_message"),
|
||||
latest_text_output: __sdk::__query_builder::Col::new(table_name, "latest_text_output"),
|
||||
latest_structured_payload_json: __sdk::__query_builder::Col::new(table_name, "latest_structured_payload_json"),
|
||||
latest_structured_payload_json: __sdk::__query_builder::Col::new(
|
||||
table_name,
|
||||
"latest_structured_payload_json",
|
||||
),
|
||||
version: __sdk::__query_builder::Col::new(table_name, "version"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
started_at: __sdk::__query_builder::Col::new(table_name, "started_at"),
|
||||
completed_at: __sdk::__query_builder::Col::new(table_name, "completed_at"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,10 +102,8 @@ impl __sdk::__query_builder::HasIxCols for AiTask {
|
||||
status: __sdk::__query_builder::IxCol::new(table_name, "status"),
|
||||
task_id: __sdk::__query_builder::IxCol::new(table_name, "task_id"),
|
||||
task_kind: __sdk::__query_builder::IxCol::new(table_name, "task_kind"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AiTask {}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
|
||||
@@ -21,8 +16,6 @@ pub struct AiTextChunkAppendInput {
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTextChunkAppendInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
|
||||
@@ -22,8 +17,6 @@ pub struct AiTextChunkSnapshot {
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTextChunkSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_stage_kind_type::AiTaskStageKind;
|
||||
|
||||
@@ -23,12 +18,10 @@ pub struct AiTextChunk {
|
||||
pub created_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AiTextChunk {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AiTextChunk`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -53,7 +46,6 @@ impl __sdk::__query_builder::HasCols for AiTextChunk {
|
||||
sequence: __sdk::__query_builder::Col::new(table_name, "sequence"),
|
||||
delta_text: __sdk::__query_builder::Col::new(table_name, "delta_text"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,10 +64,8 @@ impl __sdk::__query_builder::HasIxCols for AiTextChunk {
|
||||
AiTextChunkIxCols {
|
||||
task_id: __sdk::__query_builder::IxCol::new(table_name, "task_id"),
|
||||
text_chunk_row_id: __sdk::__query_builder::IxCol::new(table_name, "text_chunk_row_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AiTextChunk {}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_text_chunk_append_input_type::AiTextChunkAppendInput;
|
||||
use super::ai_task_procedure_result_type::AiTaskProcedureResult;
|
||||
use super::ai_text_chunk_append_input_type::AiTextChunkAppendInput;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AppendAiTextChunkAndReturnArgs {
|
||||
struct AppendAiTextChunkAndReturnArgs {
|
||||
pub input: AiTextChunkAppendInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AppendAiTextChunkAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,19 @@ impl __sdk::InModule for AppendAiTextChunkAndReturnArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait append_ai_text_chunk_and_return {
|
||||
fn append_ai_text_chunk_and_return(&self, input: AiTextChunkAppendInput,
|
||||
) {
|
||||
self.append_ai_text_chunk_and_return_then(input, |_, _| {});
|
||||
fn append_ai_text_chunk_and_return(&self, input: AiTextChunkAppendInput) {
|
||||
self.append_ai_text_chunk_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn append_ai_text_chunk_and_return_then(
|
||||
&self,
|
||||
input: AiTextChunkAppendInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<AiTaskProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<AiTaskProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +43,17 @@ impl append_ai_text_chunk_and_return for super::RemoteProcedures {
|
||||
&self,
|
||||
input: AiTextChunkAppendInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<AiTaskProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<AiTaskProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, AiTaskProcedureResult>(
|
||||
"append_ai_text_chunk_and_return",
|
||||
AppendAiTextChunkAndReturnArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, AiTaskProcedureResult>(
|
||||
"append_ai_text_chunk_and_return",
|
||||
AppendAiTextChunkAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::chapter_progression_ledger_input_type::ChapterProgressionLedgerInput;
|
||||
use super::chapter_progression_procedure_result_type::ChapterProgressionProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct ApplyChapterProgressionLedgerEntryAndReturnArgs {
|
||||
struct ApplyChapterProgressionLedgerEntryAndReturnArgs {
|
||||
pub input: ChapterProgressionLedgerInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for ApplyChapterProgressionLedgerEntryAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,22 @@ impl __sdk::InModule for ApplyChapterProgressionLedgerEntryAndReturnArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait apply_chapter_progression_ledger_entry_and_return {
|
||||
fn apply_chapter_progression_ledger_entry_and_return(&self, input: ChapterProgressionLedgerInput,
|
||||
) {
|
||||
self.apply_chapter_progression_ledger_entry_and_return_then(input, |_, _| {});
|
||||
fn apply_chapter_progression_ledger_entry_and_return(
|
||||
&self,
|
||||
input: ChapterProgressionLedgerInput,
|
||||
) {
|
||||
self.apply_chapter_progression_ledger_entry_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn apply_chapter_progression_ledger_entry_and_return_then(
|
||||
&self,
|
||||
input: ChapterProgressionLedgerInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<ChapterProgressionProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<ChapterProgressionProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +46,17 @@ impl apply_chapter_progression_ledger_entry_and_return for super::RemoteProcedur
|
||||
&self,
|
||||
input: ChapterProgressionLedgerInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<ChapterProgressionProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<ChapterProgressionProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, ChapterProgressionProcedureResult>(
|
||||
"apply_chapter_progression_ledger_entry_and_return",
|
||||
ApplyChapterProgressionLedgerEntryAndReturnArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, ChapterProgressionProcedureResult>(
|
||||
"apply_chapter_progression_ledger_entry_and_return",
|
||||
ApplyChapterProgressionLedgerEntryAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::chapter_progression_ledger_input_type::ChapterProgressionLedgerInput;
|
||||
|
||||
@@ -19,10 +14,8 @@ pub(super) struct ApplyChapterProgressionLedgerEntryArgs {
|
||||
|
||||
impl From<ApplyChapterProgressionLedgerEntryArgs> for super::Reducer {
|
||||
fn from(args: ApplyChapterProgressionLedgerEntryArgs) -> Self {
|
||||
Self::ApplyChapterProgressionLedgerEntry {
|
||||
input: args.input,
|
||||
}
|
||||
}
|
||||
Self::ApplyChapterProgressionLedgerEntry { input: args.input }
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ApplyChapterProgressionLedgerEntryArgs {
|
||||
@@ -40,9 +33,11 @@ pub trait apply_chapter_progression_ledger_entry {
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`apply_chapter_progression_ledger_entry:apply_chapter_progression_ledger_entry_then`] to run a callback after the reducer completes.
|
||||
fn apply_chapter_progression_ledger_entry(&self, input: ChapterProgressionLedgerInput,
|
||||
) -> __sdk::Result<()> {
|
||||
self.apply_chapter_progression_ledger_entry_then(input, |_, _| {})
|
||||
fn apply_chapter_progression_ledger_entry(
|
||||
&self,
|
||||
input: ChapterProgressionLedgerInput,
|
||||
) -> __sdk::Result<()> {
|
||||
self.apply_chapter_progression_ledger_entry_then(input, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `apply_chapter_progression_ledger_entry` to run as soon as possible,
|
||||
@@ -55,9 +50,11 @@ pub trait apply_chapter_progression_ledger_entry {
|
||||
&self,
|
||||
input: ChapterProgressionLedgerInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -66,11 +63,15 @@ impl apply_chapter_progression_ledger_entry for super::RemoteReducers {
|
||||
&self,
|
||||
input: ChapterProgressionLedgerInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp.invoke_reducer_with_callback(ApplyChapterProgressionLedgerEntryArgs { input, }, callback)
|
||||
self.imp.invoke_reducer_with_callback(
|
||||
ApplyChapterProgressionLedgerEntryArgs { input },
|
||||
callback,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::inventory_mutation_input_type::InventoryMutationInput;
|
||||
|
||||
@@ -19,10 +14,8 @@ pub(super) struct ApplyInventoryMutationArgs {
|
||||
|
||||
impl From<ApplyInventoryMutationArgs> for super::Reducer {
|
||||
fn from(args: ApplyInventoryMutationArgs) -> Self {
|
||||
Self::ApplyInventoryMutation {
|
||||
input: args.input,
|
||||
}
|
||||
}
|
||||
Self::ApplyInventoryMutation { input: args.input }
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ApplyInventoryMutationArgs {
|
||||
@@ -40,9 +33,8 @@ pub trait apply_inventory_mutation {
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`apply_inventory_mutation:apply_inventory_mutation_then`] to run a callback after the reducer completes.
|
||||
fn apply_inventory_mutation(&self, input: InventoryMutationInput,
|
||||
) -> __sdk::Result<()> {
|
||||
self.apply_inventory_mutation_then(input, |_, _| {})
|
||||
fn apply_inventory_mutation(&self, input: InventoryMutationInput) -> __sdk::Result<()> {
|
||||
self.apply_inventory_mutation_then(input, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `apply_inventory_mutation` to run as soon as possible,
|
||||
@@ -55,9 +47,11 @@ pub trait apply_inventory_mutation {
|
||||
&self,
|
||||
input: InventoryMutationInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -66,11 +60,13 @@ impl apply_inventory_mutation for super::RemoteReducers {
|
||||
&self,
|
||||
input: InventoryMutationInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp.invoke_reducer_with_callback(ApplyInventoryMutationArgs { input, }, callback)
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(ApplyInventoryMutationArgs { input }, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::quest_signal_apply_input_type::QuestSignalApplyInput;
|
||||
|
||||
@@ -19,10 +14,8 @@ pub(super) struct ApplyQuestSignalArgs {
|
||||
|
||||
impl From<ApplyQuestSignalArgs> for super::Reducer {
|
||||
fn from(args: ApplyQuestSignalArgs) -> Self {
|
||||
Self::ApplyQuestSignal {
|
||||
input: args.input,
|
||||
}
|
||||
}
|
||||
Self::ApplyQuestSignal { input: args.input }
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ApplyQuestSignalArgs {
|
||||
@@ -40,9 +33,8 @@ pub trait apply_quest_signal {
|
||||
/// The reducer will run asynchronously in the future,
|
||||
/// and this method provides no way to listen for its completion status.
|
||||
/// /// Use [`apply_quest_signal:apply_quest_signal_then`] to run a callback after the reducer completes.
|
||||
fn apply_quest_signal(&self, input: QuestSignalApplyInput,
|
||||
) -> __sdk::Result<()> {
|
||||
self.apply_quest_signal_then(input, |_, _| {})
|
||||
fn apply_quest_signal(&self, input: QuestSignalApplyInput) -> __sdk::Result<()> {
|
||||
self.apply_quest_signal_then(input, |_, _| {})
|
||||
}
|
||||
|
||||
/// Request that the remote module invoke the reducer `apply_quest_signal` to run as soon as possible,
|
||||
@@ -55,9 +47,11 @@ pub trait apply_quest_signal {
|
||||
&self,
|
||||
input: QuestSignalApplyInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -66,11 +60,13 @@ impl apply_quest_signal for super::RemoteReducers {
|
||||
&self,
|
||||
input: QuestSignalApplyInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp.invoke_reducer_with_callback(ApplyQuestSignalArgs { input, }, callback)
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(ApplyQuestSignalArgs { input }, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -19,13 +13,11 @@ pub struct AssetEntityBindingInput {
|
||||
pub entity_id: String,
|
||||
pub slot: String,
|
||||
pub asset_kind: String,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetEntityBindingInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_entity_binding_snapshot_type::AssetEntityBindingSnapshot;
|
||||
|
||||
@@ -15,12 +10,10 @@ use super::asset_entity_binding_snapshot_type::AssetEntityBindingSnapshot;
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AssetEntityBindingProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option::<AssetEntityBindingSnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
pub record: Option<AssetEntityBindingSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetEntityBindingProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -19,14 +13,12 @@ pub struct AssetEntityBindingSnapshot {
|
||||
pub entity_id: String,
|
||||
pub slot: String,
|
||||
pub asset_kind: String,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetEntityBindingSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -19,18 +13,16 @@ pub struct AssetEntityBinding {
|
||||
pub entity_id: String,
|
||||
pub slot: String,
|
||||
pub asset_kind: String,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetEntityBinding {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AssetEntityBinding`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -41,8 +33,8 @@ pub struct AssetEntityBindingCols {
|
||||
pub entity_id: __sdk::__query_builder::Col<AssetEntityBinding, String>,
|
||||
pub slot: __sdk::__query_builder::Col<AssetEntityBinding, String>,
|
||||
pub asset_kind: __sdk::__query_builder::Col<AssetEntityBinding, String>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AssetEntityBinding, Option::<String>>,
|
||||
pub profile_id: __sdk::__query_builder::Col<AssetEntityBinding, Option::<String>>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AssetEntityBinding, Option<String>>,
|
||||
pub profile_id: __sdk::__query_builder::Col<AssetEntityBinding, Option<String>>,
|
||||
pub created_at: __sdk::__query_builder::Col<AssetEntityBinding, __sdk::Timestamp>,
|
||||
pub updated_at: __sdk::__query_builder::Col<AssetEntityBinding, __sdk::Timestamp>,
|
||||
}
|
||||
@@ -61,7 +53,6 @@ impl __sdk::__query_builder::HasCols for AssetEntityBinding {
|
||||
profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,10 +71,8 @@ impl __sdk::__query_builder::HasIxCols for AssetEntityBinding {
|
||||
AssetEntityBindingIxCols {
|
||||
asset_object_id: __sdk::__query_builder::IxCol::new(table_name, "asset_object_id"),
|
||||
binding_id: __sdk::__query_builder::IxCol::new(table_name, "binding_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AssetEntityBinding {}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
#[derive(Copy, Eq, Hash)]
|
||||
pub enum AssetEventKind {
|
||||
ObjectConfirmed,
|
||||
|
||||
EntityBindingChanged,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AssetEventKind {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::asset_event_kind_type::AssetEventKind;
|
||||
use super::asset_event_type::AssetEvent;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `asset_event`.
|
||||
///
|
||||
/// Obtain a handle from the [`AssetEventTableAccess::asset_event`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.asset_event()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.asset_event().on_insert(...)`.
|
||||
pub struct AssetEventTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<AssetEvent>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `asset_event`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait AssetEventTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`AssetEventTableHandle`], which mediates access to the table `asset_event`.
|
||||
fn asset_event(&self) -> AssetEventTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl AssetEventTableAccess for super::RemoteTables {
|
||||
fn asset_event(&self) -> AssetEventTableHandle<'_> {
|
||||
AssetEventTableHandle {
|
||||
imp: self.imp.get_table::<AssetEvent>("asset_event"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssetEventInsertCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::EventTable for AssetEventTableHandle<'ctx> {
|
||||
type Row = AssetEvent;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = AssetEvent> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = AssetEventInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> AssetEventInsertCallbackId {
|
||||
AssetEventInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: AssetEventInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<AssetEvent>("asset_event");
|
||||
_table.add_unique_constraint::<String>("event_id", |row| &row.event_id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<AssetEvent>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<AssetEvent>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `AssetEvent`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait asset_eventQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `AssetEvent`.
|
||||
fn asset_event(&self) -> __sdk::__query_builder::Table<AssetEvent>;
|
||||
}
|
||||
|
||||
impl asset_eventQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn asset_event(&self) -> __sdk::__query_builder::Table<AssetEvent> {
|
||||
__sdk::__query_builder::Table::new("asset_event")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_event_kind_type::AssetEventKind;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AssetEvent {
|
||||
pub event_id: String,
|
||||
pub asset_object_id: String,
|
||||
pub binding_id: Option<String>,
|
||||
pub event_kind: AssetEventKind,
|
||||
pub asset_kind: String,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub entity_kind: Option<String>,
|
||||
pub entity_id: Option<String>,
|
||||
pub slot: Option<String>,
|
||||
pub occurred_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AssetEvent {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `AssetEvent`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct AssetEventCols {
|
||||
pub event_id: __sdk::__query_builder::Col<AssetEvent, String>,
|
||||
pub asset_object_id: __sdk::__query_builder::Col<AssetEvent, String>,
|
||||
pub binding_id: __sdk::__query_builder::Col<AssetEvent, Option<String>>,
|
||||
pub event_kind: __sdk::__query_builder::Col<AssetEvent, AssetEventKind>,
|
||||
pub asset_kind: __sdk::__query_builder::Col<AssetEvent, String>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AssetEvent, Option<String>>,
|
||||
pub profile_id: __sdk::__query_builder::Col<AssetEvent, Option<String>>,
|
||||
pub entity_kind: __sdk::__query_builder::Col<AssetEvent, Option<String>>,
|
||||
pub entity_id: __sdk::__query_builder::Col<AssetEvent, Option<String>>,
|
||||
pub slot: __sdk::__query_builder::Col<AssetEvent, Option<String>>,
|
||||
pub occurred_at: __sdk::__query_builder::Col<AssetEvent, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for AssetEvent {
|
||||
type Cols = AssetEventCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
AssetEventCols {
|
||||
event_id: __sdk::__query_builder::Col::new(table_name, "event_id"),
|
||||
asset_object_id: __sdk::__query_builder::Col::new(table_name, "asset_object_id"),
|
||||
binding_id: __sdk::__query_builder::Col::new(table_name, "binding_id"),
|
||||
event_kind: __sdk::__query_builder::Col::new(table_name, "event_kind"),
|
||||
asset_kind: __sdk::__query_builder::Col::new(table_name, "asset_kind"),
|
||||
owner_user_id: __sdk::__query_builder::Col::new(table_name, "owner_user_id"),
|
||||
profile_id: __sdk::__query_builder::Col::new(table_name, "profile_id"),
|
||||
entity_kind: __sdk::__query_builder::Col::new(table_name, "entity_kind"),
|
||||
entity_id: __sdk::__query_builder::Col::new(table_name, "entity_id"),
|
||||
slot: __sdk::__query_builder::Col::new(table_name, "slot"),
|
||||
occurred_at: __sdk::__query_builder::Col::new(table_name, "occurred_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `AssetEvent`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct AssetEventIxCols {
|
||||
pub asset_object_id: __sdk::__query_builder::IxCol<AssetEvent, String>,
|
||||
pub event_id: __sdk::__query_builder::IxCol<AssetEvent, String>,
|
||||
pub owner_user_id: __sdk::__query_builder::IxCol<AssetEvent, Option<String>>,
|
||||
pub profile_id: __sdk::__query_builder::IxCol<AssetEvent, Option<String>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for AssetEvent {
|
||||
type IxCols = AssetEventIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
AssetEventIxCols {
|
||||
asset_object_id: __sdk::__query_builder::IxCol::new(table_name, "asset_object_id"),
|
||||
event_id: __sdk::__query_builder::IxCol::new(table_name, "event_id"),
|
||||
owner_user_id: __sdk::__query_builder::IxCol::new(table_name, "owner_user_id"),
|
||||
profile_id: __sdk::__query_builder::IxCol::new(table_name, "profile_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -16,15 +10,13 @@ pub struct AssetHistoryEntrySnapshot {
|
||||
pub asset_object_id: String,
|
||||
pub asset_kind: String,
|
||||
pub image_src: String,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub entity_id: Option::<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub entity_id: Option<String>,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetHistoryEntrySnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -17,8 +11,6 @@ pub struct AssetHistoryListInput {
|
||||
pub limit: u32,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetHistoryListInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_history_entry_snapshot_type::AssetHistoryEntrySnapshot;
|
||||
|
||||
@@ -15,12 +10,10 @@ use super::asset_history_entry_snapshot_type::AssetHistoryEntrySnapshot;
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AssetHistoryListResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec::<AssetHistoryEntrySnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
pub entries: Vec<AssetHistoryEntrySnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetHistoryListResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -16,12 +11,8 @@ pub enum AssetObjectAccessPolicy {
|
||||
Private,
|
||||
|
||||
PublicRead,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetObjectAccessPolicy {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_object_upsert_snapshot_type::AssetObjectUpsertSnapshot;
|
||||
|
||||
@@ -15,12 +10,10 @@ use super::asset_object_upsert_snapshot_type::AssetObjectUpsertSnapshot;
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AssetObjectProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option::<AssetObjectUpsertSnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
pub record: Option<AssetObjectUpsertSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetObjectProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_object_access_policy_type::AssetObjectAccessPolicy;
|
||||
|
||||
@@ -18,25 +13,23 @@ pub struct AssetObject {
|
||||
pub bucket: String,
|
||||
pub object_key: String,
|
||||
pub access_policy: AssetObjectAccessPolicy,
|
||||
pub content_type: Option::<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub content_length: u64,
|
||||
pub content_hash: Option::<String>,
|
||||
pub content_hash: Option<String>,
|
||||
pub version: u32,
|
||||
pub source_job_id: Option::<String>,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub entity_id: Option::<String>,
|
||||
pub source_job_id: Option<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub entity_id: Option<String>,
|
||||
pub asset_kind: String,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetObject {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AssetObject`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -45,14 +38,14 @@ pub struct AssetObjectCols {
|
||||
pub bucket: __sdk::__query_builder::Col<AssetObject, String>,
|
||||
pub object_key: __sdk::__query_builder::Col<AssetObject, String>,
|
||||
pub access_policy: __sdk::__query_builder::Col<AssetObject, AssetObjectAccessPolicy>,
|
||||
pub content_type: __sdk::__query_builder::Col<AssetObject, Option::<String>>,
|
||||
pub content_type: __sdk::__query_builder::Col<AssetObject, Option<String>>,
|
||||
pub content_length: __sdk::__query_builder::Col<AssetObject, u64>,
|
||||
pub content_hash: __sdk::__query_builder::Col<AssetObject, Option::<String>>,
|
||||
pub content_hash: __sdk::__query_builder::Col<AssetObject, Option<String>>,
|
||||
pub version: __sdk::__query_builder::Col<AssetObject, u32>,
|
||||
pub source_job_id: __sdk::__query_builder::Col<AssetObject, Option::<String>>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AssetObject, Option::<String>>,
|
||||
pub profile_id: __sdk::__query_builder::Col<AssetObject, Option::<String>>,
|
||||
pub entity_id: __sdk::__query_builder::Col<AssetObject, Option::<String>>,
|
||||
pub source_job_id: __sdk::__query_builder::Col<AssetObject, Option<String>>,
|
||||
pub owner_user_id: __sdk::__query_builder::Col<AssetObject, Option<String>>,
|
||||
pub profile_id: __sdk::__query_builder::Col<AssetObject, Option<String>>,
|
||||
pub entity_id: __sdk::__query_builder::Col<AssetObject, Option<String>>,
|
||||
pub asset_kind: __sdk::__query_builder::Col<AssetObject, String>,
|
||||
pub created_at: __sdk::__query_builder::Col<AssetObject, __sdk::Timestamp>,
|
||||
pub updated_at: __sdk::__query_builder::Col<AssetObject, __sdk::Timestamp>,
|
||||
@@ -77,7 +70,6 @@ impl __sdk::__query_builder::HasCols for AssetObject {
|
||||
asset_kind: __sdk::__query_builder::Col::new(table_name, "asset_kind"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,10 +88,8 @@ impl __sdk::__query_builder::HasIxCols for AssetObject {
|
||||
AssetObjectIxCols {
|
||||
asset_kind: __sdk::__query_builder::IxCol::new(table_name, "asset_kind"),
|
||||
asset_object_id: __sdk::__query_builder::IxCol::new(table_name, "asset_object_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AssetObject {}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_object_access_policy_type::AssetObjectAccessPolicy;
|
||||
|
||||
@@ -18,20 +13,18 @@ pub struct AssetObjectUpsertInput {
|
||||
pub bucket: String,
|
||||
pub object_key: String,
|
||||
pub access_policy: AssetObjectAccessPolicy,
|
||||
pub content_type: Option::<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub content_length: u64,
|
||||
pub content_hash: Option::<String>,
|
||||
pub content_hash: Option<String>,
|
||||
pub version: u32,
|
||||
pub source_job_id: Option::<String>,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub entity_id: Option::<String>,
|
||||
pub source_job_id: Option<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub entity_id: Option<String>,
|
||||
pub asset_kind: String,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetObjectUpsertInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_object_access_policy_type::AssetObjectAccessPolicy;
|
||||
|
||||
@@ -18,21 +13,19 @@ pub struct AssetObjectUpsertSnapshot {
|
||||
pub bucket: String,
|
||||
pub object_key: String,
|
||||
pub access_policy: AssetObjectAccessPolicy,
|
||||
pub content_type: Option::<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub content_length: u64,
|
||||
pub content_hash: Option::<String>,
|
||||
pub content_hash: Option<String>,
|
||||
pub version: u32,
|
||||
pub source_job_id: Option::<String>,
|
||||
pub owner_user_id: Option::<String>,
|
||||
pub profile_id: Option::<String>,
|
||||
pub entity_id: Option::<String>,
|
||||
pub source_job_id: Option<String>,
|
||||
pub owner_user_id: Option<String>,
|
||||
pub profile_id: Option<String>,
|
||||
pub entity_id: Option<String>,
|
||||
pub asset_kind: String,
|
||||
pub created_at_micros: i64,
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AssetObjectUpsertSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::ai_task_procedure_result_type::AiTaskProcedureResult;
|
||||
use super::ai_result_reference_input_type::AiResultReferenceInput;
|
||||
use super::ai_task_procedure_result_type::AiTaskProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AttachAiResultReferenceAndReturnArgs {
|
||||
struct AttachAiResultReferenceAndReturnArgs {
|
||||
pub input: AiResultReferenceInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AttachAiResultReferenceAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,19 @@ impl __sdk::InModule for AttachAiResultReferenceAndReturnArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait attach_ai_result_reference_and_return {
|
||||
fn attach_ai_result_reference_and_return(&self, input: AiResultReferenceInput,
|
||||
) {
|
||||
self.attach_ai_result_reference_and_return_then(input, |_, _| {});
|
||||
fn attach_ai_result_reference_and_return(&self, input: AiResultReferenceInput) {
|
||||
self.attach_ai_result_reference_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn attach_ai_result_reference_and_return_then(
|
||||
&self,
|
||||
input: AiResultReferenceInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<AiTaskProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<AiTaskProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +43,17 @@ impl attach_ai_result_reference_and_return for super::RemoteProcedures {
|
||||
&self,
|
||||
input: AiResultReferenceInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<AiTaskProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<AiTaskProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, AiTaskProcedureResult>(
|
||||
"attach_ai_result_reference_and_return",
|
||||
AttachAiResultReferenceAndReturnArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, AiTaskProcedureResult>(
|
||||
"attach_ai_result_reference_and_return",
|
||||
AttachAiResultReferenceAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -17,18 +11,16 @@ pub struct AuthIdentity {
|
||||
pub user_id: String,
|
||||
pub provider: String,
|
||||
pub provider_uid: String,
|
||||
pub provider_union_id: Option::<String>,
|
||||
pub phone_e_164: Option::<String>,
|
||||
pub display_name: Option::<String>,
|
||||
pub avatar_url: Option::<String>,
|
||||
pub provider_union_id: Option<String>,
|
||||
pub phone_e_164: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthIdentity {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AuthIdentity`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -37,10 +29,10 @@ pub struct AuthIdentityCols {
|
||||
pub user_id: __sdk::__query_builder::Col<AuthIdentity, String>,
|
||||
pub provider: __sdk::__query_builder::Col<AuthIdentity, String>,
|
||||
pub provider_uid: __sdk::__query_builder::Col<AuthIdentity, String>,
|
||||
pub provider_union_id: __sdk::__query_builder::Col<AuthIdentity, Option::<String>>,
|
||||
pub phone_e_164: __sdk::__query_builder::Col<AuthIdentity, Option::<String>>,
|
||||
pub display_name: __sdk::__query_builder::Col<AuthIdentity, Option::<String>>,
|
||||
pub avatar_url: __sdk::__query_builder::Col<AuthIdentity, Option::<String>>,
|
||||
pub provider_union_id: __sdk::__query_builder::Col<AuthIdentity, Option<String>>,
|
||||
pub phone_e_164: __sdk::__query_builder::Col<AuthIdentity, Option<String>>,
|
||||
pub display_name: __sdk::__query_builder::Col<AuthIdentity, Option<String>>,
|
||||
pub avatar_url: __sdk::__query_builder::Col<AuthIdentity, Option<String>>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for AuthIdentity {
|
||||
@@ -55,7 +47,6 @@ impl __sdk::__query_builder::HasCols for AuthIdentity {
|
||||
phone_e_164: __sdk::__query_builder::Col::new(table_name, "phone_e_164"),
|
||||
display_name: __sdk::__query_builder::Col::new(table_name, "display_name"),
|
||||
avatar_url: __sdk::__query_builder::Col::new(table_name, "avatar_url"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,10 +65,8 @@ impl __sdk::__query_builder::HasIxCols for AuthIdentity {
|
||||
AuthIdentityIxCols {
|
||||
identity_id: __sdk::__query_builder::IxCol::new(table_name, "identity_id"),
|
||||
user_id: __sdk::__query_builder::IxCol::new(table_name, "user_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AuthIdentity {}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::auth_store_snapshot_import_record_type::AuthStoreSnapshotImportRecord;
|
||||
|
||||
@@ -15,12 +10,10 @@ use super::auth_store_snapshot_import_record_type::AuthStoreSnapshotImportRecord
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AuthStoreSnapshotImportProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option::<AuthStoreSnapshotImportRecord>,
|
||||
pub error_message: Option::<String>,
|
||||
pub record: Option<AuthStoreSnapshotImportRecord>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthStoreSnapshotImportProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -18,8 +12,6 @@ pub struct AuthStoreSnapshotImportRecord {
|
||||
pub imported_refresh_session_count: u32,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthStoreSnapshotImportRecord {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::auth_store_snapshot_record_type::AuthStoreSnapshotRecord;
|
||||
|
||||
@@ -15,12 +10,10 @@ use super::auth_store_snapshot_record_type::AuthStoreSnapshotRecord;
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AuthStoreSnapshotProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option::<AuthStoreSnapshotRecord>,
|
||||
pub error_message: Option::<String>,
|
||||
pub record: Option<AuthStoreSnapshotRecord>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthStoreSnapshotProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,15 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AuthStoreSnapshotRecord {
|
||||
pub snapshot_json: Option::<String>,
|
||||
pub updated_at_micros: Option::<i64>,
|
||||
pub snapshot_json: Option<String>,
|
||||
pub updated_at_micros: Option<i64>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthStoreSnapshotRecord {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -18,12 +12,10 @@ pub struct AuthStoreSnapshot {
|
||||
pub updated_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthStoreSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
/// Column accessor struct for the table `AuthStoreSnapshot`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
@@ -40,7 +32,6 @@ impl __sdk::__query_builder::HasCols for AuthStoreSnapshot {
|
||||
snapshot_id: __sdk::__query_builder::Col::new(table_name, "snapshot_id"),
|
||||
snapshot_json: __sdk::__query_builder::Col::new(table_name, "snapshot_json"),
|
||||
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,10 +48,8 @@ impl __sdk::__query_builder::HasIxCols for AuthStoreSnapshot {
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
AuthStoreSnapshotIxCols {
|
||||
snapshot_id: __sdk::__query_builder::IxCol::new(table_name, "snapshot_id"),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for AuthStoreSnapshot {}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -17,8 +11,6 @@ pub struct AuthStoreSnapshotUpsertInput {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthStoreSnapshotUpsertInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,17 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::database_migration_authorize_operator_input_type::DatabaseMigrationAuthorizeOperatorInput;
|
||||
use super::database_migration_operator_procedure_result_type::DatabaseMigrationOperatorProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct AuthorizeDatabaseMigrationOperatorArgs {
|
||||
struct AuthorizeDatabaseMigrationOperatorArgs {
|
||||
pub input: DatabaseMigrationAuthorizeOperatorInput,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for AuthorizeDatabaseMigrationOperatorArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
@@ -28,16 +22,22 @@ impl __sdk::InModule for AuthorizeDatabaseMigrationOperatorArgs {
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait authorize_database_migration_operator {
|
||||
fn authorize_database_migration_operator(&self, input: DatabaseMigrationAuthorizeOperatorInput,
|
||||
) {
|
||||
self.authorize_database_migration_operator_then(input, |_, _| {});
|
||||
fn authorize_database_migration_operator(
|
||||
&self,
|
||||
input: DatabaseMigrationAuthorizeOperatorInput,
|
||||
) {
|
||||
self.authorize_database_migration_operator_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn authorize_database_migration_operator_then(
|
||||
&self,
|
||||
input: DatabaseMigrationAuthorizeOperatorInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<DatabaseMigrationOperatorProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<DatabaseMigrationOperatorProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,13 +46,17 @@ impl authorize_database_migration_operator for super::RemoteProcedures {
|
||||
&self,
|
||||
input: DatabaseMigrationAuthorizeOperatorInput,
|
||||
|
||||
__callback: impl FnOnce(&super::ProcedureEventContext, Result<DatabaseMigrationOperatorProcedureResult, __sdk::InternalError>) + Send + 'static,
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<DatabaseMigrationOperatorProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp.invoke_procedure_with_callback::<_, DatabaseMigrationOperatorProcedureResult>(
|
||||
"authorize_database_migration_operator",
|
||||
AuthorizeDatabaseMigrationOperatorArgs { input, },
|
||||
__callback,
|
||||
);
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, DatabaseMigrationOperatorProcedureResult>(
|
||||
"authorize_database_migration_operator",
|
||||
AuthorizeDatabaseMigrationOperatorArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -16,12 +11,8 @@ pub enum BattleMode {
|
||||
Fight,
|
||||
|
||||
Spar,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl __sdk::InModule for BattleMode {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::battle_mode_type::BattleMode;
|
||||
use super::runtime_item_reward_item_snapshot_type::RuntimeItemRewardItemSnapshot;
|
||||
@@ -19,7 +14,7 @@ pub struct BattleStateInput {
|
||||
pub story_session_id: String,
|
||||
pub runtime_session_id: String,
|
||||
pub actor_user_id: String,
|
||||
pub chapter_id: Option::<String>,
|
||||
pub chapter_id: Option<String>,
|
||||
pub target_npc_id: String,
|
||||
pub target_name: String,
|
||||
pub battle_mode: BattleMode,
|
||||
@@ -30,12 +25,10 @@ pub struct BattleStateInput {
|
||||
pub target_hp: i32,
|
||||
pub target_max_hp: i32,
|
||||
pub experience_reward: u32,
|
||||
pub reward_items: Vec::<RuntimeItemRewardItemSnapshot>,
|
||||
pub reward_items: Vec<RuntimeItemRewardItemSnapshot>,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for BattleStateInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::battle_state_snapshot_type::BattleStateSnapshot;
|
||||
|
||||
@@ -15,12 +10,10 @@ use super::battle_state_snapshot_type::BattleStateSnapshot;
|
||||
#[sats(crate = __lib)]
|
||||
pub struct BattleStateProcedureResult {
|
||||
pub ok: bool,
|
||||
pub snapshot: Option::<BattleStateSnapshot>,
|
||||
pub error_message: Option::<String>,
|
||||
pub snapshot: Option<BattleStateSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for BattleStateProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{
|
||||
self as __sdk,
|
||||
__lib,
|
||||
__sats,
|
||||
__ws,
|
||||
};
|
||||
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
@@ -16,8 +10,6 @@ pub struct BattleStateQueryInput {
|
||||
pub battle_state_id: String,
|
||||
}
|
||||
|
||||
|
||||
impl __sdk::InModule for BattleStateQueryInput {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user