Close DDD refactor and remove generated asset proxy

This commit is contained in:
kdletters
2026-05-02 00:27:22 +08:00
parent fd08262bf0
commit 9d9913095d
605 changed files with 11811 additions and 10106 deletions

5
server-rs/Cargo.lock generated
View File

@@ -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",

View File

@@ -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 路由
当前本地脚本补充说明:

View File

@@ -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` 进入后端真相源。

View File

@@ -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"));

View File

@@ -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(

View File

@@ -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()
);
}
}

View File

@@ -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())

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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. 边界约束

View File

@@ -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,
}

View File

@@ -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,

View File

@@ -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. 边界约束

View File

@@ -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)。

View File

@@ -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,

View File

@@ -58,7 +58,6 @@ fn build_request(function_id: &str, option_text: &str) -> RuntimeStoryActionRequ
"optionText": option_text
})),
},
snapshot: None,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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,

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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 }

View File

@@ -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. 边界约束

View File

@@ -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)
}

View File

@@ -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 结算奖励字段非法"),
}
}
}

View File

@@ -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
);
}
}

View File

@@ -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 写入口。
当前仍刻意未做:

View File

@@ -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 {

View File

@@ -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"));
}
}

View File

@@ -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 {

View File

@@ -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` 声明的模块都有落盘文件:

View File

@@ -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);
})
{

View File

@@ -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| {

View File

@@ -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| {

View File

@@ -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(_)
));
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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")
}
}

View File

@@ -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"),
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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")
}
}

View File

@@ -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"),
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,
);
}
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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,
);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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