feat(游戏共创): 共创主题(一)数据模型与领域纯函数
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m44s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled

按 `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md §3.10` 与里程碑《共创主题与作品树-2026-10-06》的清单 A/B/F 落地(本块**不含** procedure / 路由)。

- **表**:`game_distribution_theme`(`theme_id` 主键、`name`/`summary`/`badge`/`sort_order`/`status ∈ draft|published|archived`/`created_by_user_id`/`created_at`/`updated_at`)+ `game_distribution_theme_member`(确定性主键 `member_id = "{theme_id}:{root_game_id}"`,`theme_id`、`root_game_id` 各具名 btree 索引,`sort_order`、`created_at`);归档 / 下架只改公开投影,不删成员行。`migration.rs` 白名单登记两张表(随迁移导出/导入)。
- **生成绑定**:4 个 `game_distribution_theme*` 生成文件 + `module_bindings.rs` 入口(纯追加)。生成时**不覆盖工作树**(本仓共享工作树禁止 git 写命令):先 `spacetime generate --lang rust --out-dir <仓外临时目录>`,rustfmt 后只拷回本次相关文件,避免上一轮踩过的「rustfmt 漂移把别人文件一起改」。
- **领域纯函数**(`module-game-distribution/src/theme.rs`,风格照同目录 `collection.rs`):状态合法/公开可见判定、`game_distribution_theme_member_id`、成员可见性(**委托** `collection.rs` 的判定,不复制第二套)、`game_distribution_theme_root_acceptable(has_lineage_row)`、页大小(缺省 20 / 上限 50 / 0 取默认 / 超界截断)、游标编解码(`"{createdAtMicros}:{themeId}"`,Err 文案「主题列表游标格式无效」,已核对不含 404/409 关键词)、`sort_public_themes`(`created_at` 倒序 + `theme_id` 兜底)、`page_public_themes`(排序 → retain 游标之后 → 多取一条判 has_more;**先过滤再切页**的顺序语义写进文档注释)、`sort_theme_members`(`sort_order` 升序 + `member_id` 兜底)与 `page_theme_members`。
- **契约**:`shared-contracts` 新增 `GameDistributionThemeStatus` / `GameDistributionThemeReference` / `GameDistributionThemeSummary` / `GameDistributionThemeListResponse` / `GameDistributionThemeDetail` / `GameDistributionCreateThemeRequest` / `GameDistributionUpdateThemeRequest` / `GameDistributionUpsertThemeMemberRequest`,`GameDistributionGameSummary` 追加可选 `themes`(按 `collected` 先例只在详情路径下发);TS 镜像同步;parity 登记 **58 组**(响应构建器待路由落地后再登记,parity 要求函数真实存在)。
- **数据契约表目录**:补两张表小节,94 → **96**。
- **测试**:`theme.rs` 16 个单测(成员 id 确定性、状态三态与公开可见、成员可见性逐格与收藏判定等价、根判定、页大小边界、游标往返与非法输入、排序稳定性与同 `sort_order` 兜底、分页不重不漏)。
- **门禁**:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` **97 passed**;`cargo test -p spacetime-module` 274 passed / 1 ignored;DTO parity **58 组**;`check:spacetime-schema` 0(**96** tables);`check:server-rs-ddd` 0;`check:encoding` 0(5388 files);`cargo fmt --all -- --check` 0;`git diff --check` 0(8 个改动文件 0 删除行)。
This commit is contained in:
2026-10-06 01:46:23 +08:00
parent 063c04a1b3
commit 2fa201e0dc
13 changed files with 1642 additions and 0 deletions
@@ -517,6 +517,27 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- 读取口径:`list_game_distribution_collections_and_return` 按 `user_id` 索引取行、按 `created_at` 倒序(同刻按 `game_id` 升序)稳定排序,逐条用**既有** `public_game_distribution_snapshot` 投影,只返回当前公开可读的作品(`module_game_distribution::game_distribution_collection_visible`)。下架 / 软删除的行在**投影时被跳过但不删除**,作品重新公开后自动回到列表。公开详情的 `collected` 由 `is_game_distribution_collected_and_return` 单独查询,不改动既有公开快照契约(`api-server` 在登录时把它并入公开详情负载,匿名不发该键)。
- 迁移:随迁移导入导出(用户态事实),作品下架不删除行。`migration_tables!` 已登记该表。
### `game_distribution_theme`
- Rust 结构体:`GameDistributionTheme`,源码:`server-rs/crates/spacetime-module/src/game_distribution.rs`;2026-10-06 新增。私有表,仅通过受信 API 服务身份的 procedure 与 BFF 提供投影。
- 用途:平台 / 运营**命名**的「共创主题」——一棵(或一组)作品树的归组实体。主题不是作品的属性,而是运营叙事:作品在游戏 Tab 里仍作为独立作品展示,主题只是额外的归组维度。字段:`theme_id: String`(主键,形如 `theme-*`,由服务端签发、不接受客户端指定)、`name`(运营命名,不是根作品标题的派生)、`summary`、`badge`(角标短文本,本轮与 `name` 共同承担「封面」呈现职责,无封面图列)、`sort_order: i64`(成员排序权重)、`status: String`、`created_by_user_id: String`(创建者审计,不参与鉴权判定)、`created_at: Timestamp`、`updated_at: Timestamp`。
- 状态口径:`status` 三态白名单 `draft` / `published` / `archived`(`module_game_distribution::GAME_DISTRIBUTION_THEME_STATUSES`);只有 `published` 进入公开投影(`game_distribution_theme_public_visible`),`draft` 与 `archived` 在公开列表 / 公开详情 / 作品详情 `themes` 里都不可见,后台仍可见全量。归档**不是删除**:行与成员行都保留,语义可逆。
- 索引:`by_game_distribution_theme_created_at`。公开列表按创建时间倒序 + `theme_id` 升序兜底翻页(游标 `"{created_at_micros}:{theme_id}"`,默认 20 / 上限 50 / 超界截断 / 非法游标 400);`sort_order` 只用于主题内成员排序与后台列表展示,不参与公开列表排序。
- 迁移:随迁移导出/导入(运营业务事实);归档只改变公开投影,不删除行。`migration_tables!` 已登记该表。
- 写入路径:后台写接口(`create_game_distribution_theme_and_return` / `update_game_distribution_theme_and_return`)属后续里程碑;本轮只落表、生成绑定、迁移登记与契约。
### `game_distribution_theme_member`
- Rust 结构体:`GameDistributionThemeMember`,源码:`server-rs/crates/spacetime-module/src/game_distribution.rs`;2026-10-06 新增。私有表。
- 用途:「主题 ↔ 根作品」的运营归组事实。字段:`member_id: String`(主键)、`theme_id`、`root_game_id`、`sort_order: i64`、`created_at: Timestamp`。
- 只允许**根作品**:根 = 该作品没有血缘行(第 0 代)。判定事实由调用方用血缘点查给出(`game_distribution_lineage().game_id().find(&game_id)` 命中即有行 ⇒ 非根),规则由纯函数 `game_distribution_theme_root_acceptable` 表达;非根写入被拒(`THEME_MEMBER_NOT_ROOT` 409)。理由:主题页呈现的作品树由 `/games/{id}/lineage` **按根**聚合,若允许子作品也入主题,同一棵树会在主题页里出现多次。
- 防重:主键是确定性构造的 `{theme_id}:{root_game_id}`(`module_game_distribution::game_distribution_theme_member_id`),同一 (主题, 根) 在结构上不可能出现第二行——去重由主键约束本身承担,而不是「事务里先查后写」。因此**不需要**额外的 `(theme_id, root_game_id)` 唯一索引。跨主题的同一根会有**多行**(每主题一行),这是预期行为而不是重复数据。
- 索引:`by_game_distribution_theme_member_theme_id`(主题页按主题取成员)、`by_game_distribution_theme_member_root_game_id`(作品详情按根反查所属公开主题,因此第 N 代作品也能看到自己的主题)。
- 可见性:成员只出现在「作品公开未删且存在当前公开版本」时——判定**委托**收藏的同一条口径 `module_game_distribution::game_distribution_theme_member_visible`(内部即 `game_distribution_collection_visible`),不新写第二个同义判定。作品下架 / 软删除时该成员在**投影里跳过但不删除行**,作品重新公开后自动回到主题页;主题详情的 `roots` 与列表的 `memberCount` 用同一判定,因此两个数永远一致。
- 排序:主题内成员按 `sort_order` 升序 + `member_id` 升序兜底(`sort_order` 允许重复,兜底键保证全序、翻页/渲染不重不漏);可见性过滤必须发生在排序切页之前。
- 迁移:随迁移导出/导入(运营业务事实);归档主题与下架作品都不删除成员行。`migration_tables!` 已登记该表。
- 写入路径:后台成员写接口(`upsert_game_distribution_theme_member_and_return` / `remove_game_distribution_theme_member_and_return`)属后续里程碑;本轮只落表、生成绑定、迁移登记与契约。
### `game_distribution_review`
- Rust 结构体:`GameDistributionReview`,源码:`server-rs/crates/spacetime-module/src/game_distribution.rs`;私有表,仅通过受信 API 服务身份的 procedure 与 BFF 提供评价投影。
@@ -296,6 +296,14 @@ export type GameDistributionGame = {
* 收藏按钮态。公开目录不返回。值来自服务端真实投影,不是前端本地状态。
*/
collected?: boolean;
/**
* 该作品所属的**公开**共创主题(先取作品的根、再按根反查,跨主题多归属因此是数组)。
*
* **只有公开详情返回**(匿名与登录都返回;没有所属主题时是空数组,展示层据此不渲染空容器),
* 公开目录与作者侧不带这个键。第 N 代作品同样能看到主题——反查用的是根,不是作品自身。
* 排序与公开主题列表共用同一份比较器(创建时间倒序 + themeId 升序兜底)。
*/
themes?: GameDistributionThemeReference[];
playCount: number;
createdAt: string;
};
@@ -318,6 +326,101 @@ export type GameDistributionMyCollectionsResponse = {
nextCursor?: string | null;
};
/**
* 共创主题的三态处置:`draft`(未发布)/ `published`(公开可见)/ `archived`(归档下架)。
*
* 公开侧只出现 `published`;`archived` 不是删除(主题行与成员行都保留,只是不再公开)。
* 后台写接口的 `status` 用这个联合类型,非法取值由服务端拒绝(400)。
*/
export type GameDistributionThemeStatus = 'draft' | 'published' | 'archived';
/**
* 作品详情「所属主题」增量的单个条目:只够渲染一个跳转入口(主题页里才有完整信息)。
*/
export type GameDistributionThemeReference = {
themeId: string;
name: string;
badge: string;
};
/**
* 公开主题列表的单条:主题名、简介、角标与**当前公开可见成员数**。
*
* `memberCount` 与详情响应的 `roots` 长度是同一个数(同一份成员可见性判定),不含草稿 /
* 已下架成员;服务端不回报「全部成员行数」,客户端也不该用两个数互相推算。
*/
export type GameDistributionThemeSummary = {
themeId: string;
name: string;
summary: string;
badge: string;
memberCount: number;
};
/**
* 公开主题列表接口(`GET /api/game-distribution/themes`)的响应体。
*
* 只含 `published` 主题。分页沿用既有游标惯例:默认 20 条、上限 50 条、超界截断;
* `nextCursor` 是真实游标(`"{createdAtMicros}:{themeId}"`),最后一页为 `null`;游标格式非法时
* 服务端返回 400(不是 200 的空页)。排序为创建时间倒序 + `themeId` 升序兜底(全序,不重不漏)。
*/
export type GameDistributionThemeListResponse = {
themes: GameDistributionThemeSummary[];
nextCursor?: string | null;
};
/**
* 公开主题详情接口(`GET /api/game-distribution/themes/{themeId}`)的响应体。
*
* 顶层扁平形状与 `GET /games/{gameId}` 同形,不引入第二套包装。`roots` 逐条是公开目录同一份
* 作品投影,排序为 `sortOrder` 升序 + `memberId` 升序兜底。主题不存在或未发布时接口返回 404
* (不返回空壳);已发布但可见成员为空时返回 200 + 空 `roots`(展示层按空态而不是错误渲染)。
*/
export type GameDistributionThemeDetail = {
themeId: string;
name: string;
summary: string;
badge: string;
memberCount: number;
roots: GameDistributionGame[];
};
/**
* 后台创建主题(`POST /admin/api/game-distribution/themes`)的请求体。
*
* `name` 必填且非空;其余字段省略时按「空文本 / 排序 0 / `draft`」落库(与表列默认一致)。
*/
export type GameDistributionCreateThemeRequest = {
name: string;
summary?: string;
badge?: string;
sortOrder?: number;
status?: GameDistributionThemeStatus;
};
/**
* 后台更新主题(`PUT /admin/api/game-distribution/themes/{themeId}`)的请求体。
*
* **整体覆盖**这几个字段(不是部分更新):字段都必填,避免「省略 = 保留旧值」与「省略 = 清空」
* 两种解释在客户端之间漂移。
*/
export type GameDistributionUpdateThemeRequest = {
name: string;
summary: string;
badge: string;
sortOrder: number;
status: GameDistributionThemeStatus;
};
/**
* 后台增 / 改成员(`PUT …/themes/{themeId}/members/{rootGameId}`)的请求体。
*
* 成员身份完全由路径给出,body 只承载运营权重;重复调用由确定性主键保证幂等。
*/
export type GameDistributionUpsertThemeMemberRequest = {
sortOrder?: number;
};
export type GameDistributionCreateGameRequest = {
/** 发布方本地项目标识;同一作者重复发布会复用既有 gameId。 */
localProjectId?: string | null;
@@ -87,6 +87,21 @@ const PAIRS = [
'GameDistributionMyCollectionsResponse',
'GameDistributionMyCollectionsResponse',
],
// 共创主题:三态枚举、详情增量条目、公开列表 / 详情与后台写请求逐字段对齐。
// 公开列表 / 详情的手拼响应构建器(`public_themes_payload` / `public_theme_detail_payload`、
// 以及作品详情的 `themes` 增量)要等 api-server 路由落地后才登记进 `RESPONSE_BUILDERS`——
// 那里要求函数真实存在,登记不存在的构建器会让本检查直接失败。
['GameDistributionThemeStatus', 'GameDistributionThemeStatus'],
['GameDistributionThemeReference', 'GameDistributionThemeReference'],
['GameDistributionThemeSummary', 'GameDistributionThemeSummary'],
['GameDistributionThemeListResponse', 'GameDistributionThemeListResponse'],
['GameDistributionThemeDetail', 'GameDistributionThemeDetail'],
['GameDistributionCreateThemeRequest', 'GameDistributionCreateThemeRequest'],
['GameDistributionUpdateThemeRequest', 'GameDistributionUpdateThemeRequest'],
[
'GameDistributionUpsertThemeMemberRequest',
'GameDistributionUpsertThemeMemberRequest',
],
['AdminGameReviewGamesQuery', 'AdminGameReviewGamesQuery'],
['AdminGameReviewGame', 'AdminGameReviewGame'],
['AdminGameReviewGamesResponse', 'AdminGameReviewGamesResponse'],
@@ -9,6 +9,7 @@ mod package;
mod project_bundle;
mod release;
mod reviews;
mod theme;
pub use reviews::{
ReviewModerationValidationError, UserReviewValidationError, normalize_review_comment,
@@ -57,3 +58,14 @@ pub use release::{
MAX_RELEASE_ASSET_BYTES, ReleaseAssetError, extract_release_asset,
normalize_release_asset_path, release_asset_content_type,
};
pub use theme::{
GAME_DISTRIBUTION_THEME_PAGE_LIMIT_DEFAULT, GAME_DISTRIBUTION_THEME_PAGE_LIMIT_MAX,
GAME_DISTRIBUTION_THEME_STATUS_ARCHIVED, GAME_DISTRIBUTION_THEME_STATUS_DRAFT,
GAME_DISTRIBUTION_THEME_STATUS_PUBLISHED, GAME_DISTRIBUTION_THEME_STATUSES,
GameDistributionThemeMemberPageItem, GameDistributionThemePageItem,
encode_game_distribution_theme_cursor, game_distribution_theme_member_id,
game_distribution_theme_member_visible, game_distribution_theme_page_limit,
game_distribution_theme_public_visible, game_distribution_theme_root_acceptable,
game_distribution_theme_status_valid, page_public_themes, page_theme_members,
parse_game_distribution_theme_cursor, sort_public_themes, sort_theme_members,
};
File diff suppressed because it is too large Load Diff
@@ -223,6 +223,13 @@ pub struct GameDistributionGameSummary {
/// 的真实投影,不是客户端本地状态。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub collected: Option<bool>,
/// 该作品所属的**公开**共创主题:先取作品的根、再按根反查成员行(跨主题多归属,因此是数组)。
///
/// **只有公开详情下发**(匿名与登录都发;没有所属主题时发空数组,展示层不渲染空容器);
/// 公开列表与作者侧不发该键。第 N 代作品同样能看到主题——反查用的是**根**,不是作品自身。
/// 排序与公开主题列表共用同一份比较器(创建时间倒序 + `theme_id` 升序兜底)。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub themes: Option<Vec<GameDistributionThemeReference>>,
}
/// 作品级共创(Fork)授权档位:只允许单向提升开放度。
@@ -393,6 +400,121 @@ pub struct GameDistributionMyCollectionsResponse {
pub next_cursor: Option<String>,
}
/// 共创主题的三态处置:`draft`(未发布)/ `published`(公开可见)/ `archived`(归档下架)。
///
/// 后台写接口的入参用这个枚举而不是裸字符串:非法取值在反序列化阶段即被拒绝(api-server 映射
/// 400),不会带着一个「未定义状态」进入事务。模块侧的数据列仍是字符串,配套白名单是
/// `module_game_distribution::GAME_DISTRIBUTION_THEME_STATUSES`。
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum GameDistributionThemeStatus {
/// 已创建但未对外发布:公开侧任何投影都看不到。
#[default]
Draft,
/// 已发布:公开列表、公开详情与作品详情的 `themes` 都会出现。
Published,
/// 已归档下架:不再公开,但主题行与成员行都保留。
Archived,
}
/// 作品详情「所属主题」增量的单个条目:只够渲染一个跳转入口。
///
/// 不含简介与成员数——详情页只需要「这条作品属于哪些主题」和一个能点进去的名字与角标,
/// 完整信息在主题页(`GameDistributionThemeDetail`)取。
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionThemeReference {
pub theme_id: String,
pub name: String,
pub badge: String,
}
/// 公开主题列表的单条:主题名、简介、角标与**当前公开可见成员数**。
///
/// `member_count` 与详情响应的 `roots` 长度是同一个数(同一份成员可见性判定):若回报「全部成员
/// 行数」,运营就能通过计数变化探测存在草稿 / 被下架成员,且它会与 `roots` 长度对不上。
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionThemeSummary {
pub theme_id: String,
pub name: String,
pub summary: String,
pub badge: String,
pub member_count: u64,
}
/// 公开主题列表接口(`GET /api/game-distribution/themes`)的响应体。
///
/// 只含 `status == published` 的主题。分页沿用既有游标惯例:默认 20 条、上限 50 条、超界截断;
/// `next_cursor` 是真实游标(`"{created_at_micros}:{theme_id}"`),最后一页为 `null`;游标格式
/// 非法时服务端返回 400。排序为创建时间倒序 + `theme_id` 升序兜底(全序,翻页不重不漏)。
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionThemeListResponse {
pub themes: Vec<GameDistributionThemeSummary>,
#[serde(default)]
pub next_cursor: Option<String>,
}
/// 公开主题详情接口(`GET /api/game-distribution/themes/{theme_id}`)的响应体。
///
/// 顶层扁平形状与 `GET /games/{gameId}` 同形,不引入第二套包装。`roots` 逐条是**公开目录同一份**
/// 作品投影(`GameDistributionGameSummary`),排序为 `sort_order` 升序 + `member_id` 升序兜底。
/// 主题不存在或未发布时接口返回 404(不返回空壳);已发布但可见成员为空时返回 200 + 空 `roots`。
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionThemeDetail {
pub theme_id: String,
pub name: String,
pub summary: String,
pub badge: String,
pub member_count: u64,
pub roots: Vec<GameDistributionGameSummary>,
}
/// 后台创建主题(`POST /admin/api/game-distribution/themes`)的请求体。
///
/// `name` 必填且非空(空名由 api-server 映射 400);其余字段省略时按「空文本 / 排序 0 /
/// `draft`」落库——与表列默认一致,因此「省略」与「显式传默认」是同一个请求,幂等摘要也相同。
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionCreateThemeRequest {
pub name: String,
#[serde(default)]
pub summary: String,
#[serde(default)]
pub badge: String,
#[serde(default)]
pub sort_order: i64,
#[serde(default)]
pub status: GameDistributionThemeStatus,
}
/// 后台更新主题(`PUT /admin/api/game-distribution/themes/{theme_id}`)的请求体。
///
/// **整体覆盖**这几个字段(不是部分更新):字段都必填,避免「省略 = 保留旧值」与「省略 = 清空」
/// 两种解释在客户端之间漂移。`updated_at` 每次生效即刷新。
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionUpdateThemeRequest {
pub name: String,
pub summary: String,
pub badge: String,
pub sort_order: i64,
pub status: GameDistributionThemeStatus,
}
/// 后台增 / 改成员(`PUT /admin/api/game-distribution/themes/{theme_id}/members/{root_game_id}`)的请求体。
///
/// 成员身份完全由路径给出(`theme_id` + `root_game_id`),body 只承载运营权重;重复调用由确定性
/// 主键保证幂等(同主题同根只有一行,`created_at` 首次写入后不再变)。
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionUpsertThemeMemberRequest {
#[serde(default)]
pub sort_order: i64,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GameDistributionCreateGameRequest {
@@ -487,6 +487,10 @@ pub mod game_distribution_review_type;
pub mod game_distribution_set_fork_authorization_input_type;
pub mod game_distribution_submit_review_input_type;
pub mod game_distribution_suspend_input_type;
pub mod game_distribution_theme_member_table;
pub mod game_distribution_theme_member_type;
pub mod game_distribution_theme_table;
pub mod game_distribution_theme_type;
pub mod game_distribution_uncollect_game_input_type;
pub mod game_distribution_unpublish_input_type;
pub mod game_distribution_update_metadata_input_type;
@@ -1397,6 +1401,10 @@ pub use game_distribution_review_type::GameDistributionReview;
pub use game_distribution_set_fork_authorization_input_type::GameDistributionSetForkAuthorizationInput;
pub use game_distribution_submit_review_input_type::GameDistributionSubmitReviewInput;
pub use game_distribution_suspend_input_type::GameDistributionSuspendInput;
pub use game_distribution_theme_member_table::*;
pub use game_distribution_theme_member_type::GameDistributionThemeMember;
pub use game_distribution_theme_table::*;
pub use game_distribution_theme_type::GameDistributionTheme;
pub use game_distribution_uncollect_game_input_type::GameDistributionUncollectGameInput;
pub use game_distribution_unpublish_input_type::GameDistributionUnpublishInput;
pub use game_distribution_update_metadata_input_type::GameDistributionUpdateMetadataInput;
@@ -1985,6 +1993,8 @@ pub struct DbUpdate {
game_distribution_review: __sdk::TableUpdate<GameDistributionReview>,
game_distribution_review_moderation_log:
__sdk::TableUpdate<GameDistributionReviewModerationLog>,
game_distribution_theme: __sdk::TableUpdate<GameDistributionTheme>,
game_distribution_theme_member: __sdk::TableUpdate<GameDistributionThemeMember>,
game_distribution_version: __sdk::TableUpdate<GameDistributionVersion>,
llm_router_account: __sdk::TableUpdate<LlmRouterAccount>,
llm_router_billing_checkpoint: __sdk::TableUpdate<LlmRouterBillingCheckpoint>,
@@ -2221,6 +2231,14 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
)?,
)
}
"game_distribution_theme" => db_update.game_distribution_theme.append(
game_distribution_theme_table::parse_table_update(table_update)?,
),
"game_distribution_theme_member" => {
db_update.game_distribution_theme_member.append(
game_distribution_theme_member_table::parse_table_update(table_update)?,
)
}
"game_distribution_version" => db_update.game_distribution_version.append(
game_distribution_version_table::parse_table_update(table_update)?,
),
@@ -2653,6 +2671,18 @@ impl __sdk::DbUpdate for DbUpdate {
&self.game_distribution_review_moderation_log,
)
.with_updates_by_pk(|row| &row.operation_id);
diff.game_distribution_theme = cache
.apply_diff_to_table::<GameDistributionTheme>(
"game_distribution_theme",
&self.game_distribution_theme,
)
.with_updates_by_pk(|row| &row.theme_id);
diff.game_distribution_theme_member = cache
.apply_diff_to_table::<GameDistributionThemeMember>(
"game_distribution_theme_member",
&self.game_distribution_theme_member,
)
.with_updates_by_pk(|row| &row.member_id);
diff.game_distribution_version = cache
.apply_diff_to_table::<GameDistributionVersion>(
"game_distribution_version",
@@ -3044,6 +3074,12 @@ impl __sdk::DbUpdate for DbUpdate {
"game_distribution_review_moderation_log" => db_update
.game_distribution_review_moderation_log
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"game_distribution_theme" => db_update
.game_distribution_theme
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"game_distribution_theme_member" => db_update
.game_distribution_theme_member
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
"game_distribution_version" => db_update
.game_distribution_version
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
@@ -3339,6 +3375,12 @@ impl __sdk::DbUpdate for DbUpdate {
"game_distribution_review_moderation_log" => db_update
.game_distribution_review_moderation_log
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"game_distribution_theme" => db_update
.game_distribution_theme
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"game_distribution_theme_member" => db_update
.game_distribution_theme_member
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
"game_distribution_version" => db_update
.game_distribution_version
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
@@ -3542,6 +3584,8 @@ pub struct AppliedDiff<'r> {
game_distribution_review: __sdk::TableAppliedDiff<'r, GameDistributionReview>,
game_distribution_review_moderation_log:
__sdk::TableAppliedDiff<'r, GameDistributionReviewModerationLog>,
game_distribution_theme: __sdk::TableAppliedDiff<'r, GameDistributionTheme>,
game_distribution_theme_member: __sdk::TableAppliedDiff<'r, GameDistributionThemeMember>,
game_distribution_version: __sdk::TableAppliedDiff<'r, GameDistributionVersion>,
llm_router_account: __sdk::TableAppliedDiff<'r, LlmRouterAccount>,
llm_router_billing_checkpoint: __sdk::TableAppliedDiff<'r, LlmRouterBillingCheckpoint>,
@@ -3842,6 +3886,16 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
&self.game_distribution_review_moderation_log,
event,
);
callbacks.invoke_table_row_callbacks::<GameDistributionTheme>(
"game_distribution_theme",
&self.game_distribution_theme,
event,
);
callbacks.invoke_table_row_callbacks::<GameDistributionThemeMember>(
"game_distribution_theme_member",
&self.game_distribution_theme_member,
event,
);
callbacks.invoke_table_row_callbacks::<GameDistributionVersion>(
"game_distribution_version",
&self.game_distribution_version,
@@ -4768,6 +4822,8 @@ impl __sdk::SpacetimeModule for RemoteModule {
game_distribution_lineage_table::register_table(client_cache);
game_distribution_review_table::register_table(client_cache);
game_distribution_review_moderation_log_table::register_table(client_cache);
game_distribution_theme_table::register_table(client_cache);
game_distribution_theme_member_table::register_table(client_cache);
game_distribution_version_table::register_table(client_cache);
llm_router_account_table::register_table(client_cache);
llm_router_billing_checkpoint_table::register_table(client_cache);
@@ -4864,6 +4920,8 @@ impl __sdk::SpacetimeModule for RemoteModule {
"game_distribution_lineage",
"game_distribution_review",
"game_distribution_review_moderation_log",
"game_distribution_theme",
"game_distribution_theme_member",
"game_distribution_version",
"llm_router_account",
"llm_router_billing_checkpoint",
@@ -0,0 +1,238 @@
// 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::game_distribution_theme_member_type::GameDistributionThemeMember;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `game_distribution_theme_member`.
///
/// Obtain a handle from the [`GameDistributionThemeMemberTableAccess::game_distribution_theme_member`] method on [`super::RemoteTables`],
/// like `ctx.db.game_distribution_theme_member()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.game_distribution_theme_member().on_insert(...)`.
pub struct GameDistributionThemeMemberTableHandle<'ctx> {
imp: __sdk::TableHandle<GameDistributionThemeMember>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
/// Lifetime-aware accessor marker for the table `game_distribution_theme_member`.
pub struct GameDistributionThemeMemberTableAccessor;
impl __sdk::TableAccessor<super::RemoteTables> for GameDistributionThemeMemberTableAccessor {
type Row = GameDistributionThemeMember;
type Handle<'db> = GameDistributionThemeMemberTableHandle<'db>;
fn get<'db>(db: &'db super::RemoteTables) -> Self::Handle<'db> {
db.game_distribution_theme_member()
}
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `game_distribution_theme_member`.
///
/// Implemented for [`super::RemoteTables`].
pub trait GameDistributionThemeMemberTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`GameDistributionThemeMemberTableHandle`], which mediates access to the table `game_distribution_theme_member`.
fn game_distribution_theme_member(&self) -> GameDistributionThemeMemberTableHandle<'_>;
}
impl GameDistributionThemeMemberTableAccess for super::RemoteTables {
fn game_distribution_theme_member(&self) -> GameDistributionThemeMemberTableHandle<'_> {
GameDistributionThemeMemberTableHandle {
imp: self
.imp
.get_table::<GameDistributionThemeMember>("game_distribution_theme_member"),
ctx: std::marker::PhantomData,
}
}
}
pub struct GameDistributionThemeMemberInsertCallbackId(__sdk::CallbackId);
pub struct GameDistributionThemeMemberDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableLike for GameDistributionThemeMemberTableHandle<'ctx> {
type Row = GameDistributionThemeMember;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = GameDistributionThemeMember> + '_ {
self.imp.iter()
}
}
impl<'ctx> __sdk::Table for GameDistributionThemeMemberTableHandle<'ctx> {
type Row = GameDistributionThemeMember;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = GameDistributionThemeMember> + '_ {
self.imp.iter()
}
type InsertCallbackId = GameDistributionThemeMemberInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeMemberInsertCallbackId {
GameDistributionThemeMemberInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: GameDistributionThemeMemberInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = GameDistributionThemeMemberDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeMemberDeleteCallbackId {
GameDistributionThemeMemberDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: GameDistributionThemeMemberDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
impl<'ctx> __sdk::WithInsert for GameDistributionThemeMemberTableHandle<'ctx> {
type InsertCallbackId = GameDistributionThemeMemberInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeMemberInsertCallbackId {
GameDistributionThemeMemberInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: GameDistributionThemeMemberInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
}
impl<'ctx> __sdk::WithDelete for GameDistributionThemeMemberTableHandle<'ctx> {
type DeleteCallbackId = GameDistributionThemeMemberDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeMemberDeleteCallbackId {
GameDistributionThemeMemberDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: GameDistributionThemeMemberDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct GameDistributionThemeMemberUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for GameDistributionThemeMemberTableHandle<'ctx> {
type UpdateCallbackId = GameDistributionThemeMemberUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> GameDistributionThemeMemberUpdateCallbackId {
GameDistributionThemeMemberUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: GameDistributionThemeMemberUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
impl<'ctx> __sdk::WithUpdate for GameDistributionThemeMemberTableHandle<'ctx> {
type UpdateCallbackId = GameDistributionThemeMemberUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> GameDistributionThemeMemberUpdateCallbackId {
GameDistributionThemeMemberUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: GameDistributionThemeMemberUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `member_id` unique index on the table `game_distribution_theme_member`,
/// which allows point queries on the field of the same name
/// via the [`GameDistributionThemeMemberMemberIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.game_distribution_theme_member().member_id().find(...)`.
pub struct GameDistributionThemeMemberMemberIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<GameDistributionThemeMember, String>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> GameDistributionThemeMemberTableHandle<'ctx> {
/// Get a handle on the `member_id` unique index on the table `game_distribution_theme_member`.
pub fn member_id(&self) -> GameDistributionThemeMemberMemberIdUnique<'ctx> {
GameDistributionThemeMemberMemberIdUnique {
imp: self.imp.get_unique_constraint::<String>("member_id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> GameDistributionThemeMemberMemberIdUnique<'ctx> {
/// Find the subscribed row whose `member_id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &String) -> Option<GameDistributionThemeMember> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache
.get_or_make_table::<GameDistributionThemeMember>("game_distribution_theme_member");
_table.add_unique_constraint::<String>("member_id", |row| &row.member_id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<GameDistributionThemeMember>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse(
"TableUpdate<GameDistributionThemeMember>",
"TableUpdate",
)
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `GameDistributionThemeMember`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait game_distribution_theme_memberQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `GameDistributionThemeMember`.
fn game_distribution_theme_member(
&self,
) -> __sdk::__query_builder::Table<GameDistributionThemeMember>;
}
impl game_distribution_theme_memberQueryTableAccess for __sdk::QueryTableAccessor {
fn game_distribution_theme_member(
&self,
) -> __sdk::__query_builder::Table<GameDistributionThemeMember> {
__sdk::__query_builder::Table::new("game_distribution_theme_member")
}
}
@@ -0,0 +1,65 @@
// 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)]
pub struct GameDistributionThemeMember {
pub member_id: String,
pub theme_id: String,
pub root_game_id: String,
pub sort_order: i64,
pub created_at: __sdk::Timestamp,
}
impl __sdk::InModule for GameDistributionThemeMember {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `GameDistributionThemeMember`.
///
/// Provides typed access to columns for query building.
pub struct GameDistributionThemeMemberCols {
pub member_id: __sdk::__query_builder::Col<GameDistributionThemeMember, String>,
pub theme_id: __sdk::__query_builder::Col<GameDistributionThemeMember, String>,
pub root_game_id: __sdk::__query_builder::Col<GameDistributionThemeMember, String>,
pub sort_order: __sdk::__query_builder::Col<GameDistributionThemeMember, i64>,
pub created_at: __sdk::__query_builder::Col<GameDistributionThemeMember, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for GameDistributionThemeMember {
type Cols = GameDistributionThemeMemberCols;
fn cols(table_name: &'static str) -> Self::Cols {
GameDistributionThemeMemberCols {
member_id: __sdk::__query_builder::Col::new(table_name, "member_id"),
theme_id: __sdk::__query_builder::Col::new(table_name, "theme_id"),
root_game_id: __sdk::__query_builder::Col::new(table_name, "root_game_id"),
sort_order: __sdk::__query_builder::Col::new(table_name, "sort_order"),
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
}
}
}
/// Indexed column accessor struct for the table `GameDistributionThemeMember`.
///
/// Provides typed access to indexed columns for query building.
pub struct GameDistributionThemeMemberIxCols {
pub member_id: __sdk::__query_builder::IxCol<GameDistributionThemeMember, String>,
pub root_game_id: __sdk::__query_builder::IxCol<GameDistributionThemeMember, String>,
pub theme_id: __sdk::__query_builder::IxCol<GameDistributionThemeMember, String>,
}
impl __sdk::__query_builder::HasIxCols for GameDistributionThemeMember {
type IxCols = GameDistributionThemeMemberIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
GameDistributionThemeMemberIxCols {
member_id: __sdk::__query_builder::IxCol::new(table_name, "member_id"),
root_game_id: __sdk::__query_builder::IxCol::new(table_name, "root_game_id"),
theme_id: __sdk::__query_builder::IxCol::new(table_name, "theme_id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for GameDistributionThemeMember {}
@@ -0,0 +1,230 @@
// 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::game_distribution_theme_type::GameDistributionTheme;
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
/// Table handle for the table `game_distribution_theme`.
///
/// Obtain a handle from the [`GameDistributionThemeTableAccess::game_distribution_theme`] method on [`super::RemoteTables`],
/// like `ctx.db.game_distribution_theme()`.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.game_distribution_theme().on_insert(...)`.
pub struct GameDistributionThemeTableHandle<'ctx> {
imp: __sdk::TableHandle<GameDistributionTheme>,
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
/// Lifetime-aware accessor marker for the table `game_distribution_theme`.
pub struct GameDistributionThemeTableAccessor;
impl __sdk::TableAccessor<super::RemoteTables> for GameDistributionThemeTableAccessor {
type Row = GameDistributionTheme;
type Handle<'db> = GameDistributionThemeTableHandle<'db>;
fn get<'db>(db: &'db super::RemoteTables) -> Self::Handle<'db> {
db.game_distribution_theme()
}
}
#[allow(non_camel_case_types)]
/// Extension trait for access to the table `game_distribution_theme`.
///
/// Implemented for [`super::RemoteTables`].
pub trait GameDistributionThemeTableAccess {
#[allow(non_snake_case)]
/// Obtain a [`GameDistributionThemeTableHandle`], which mediates access to the table `game_distribution_theme`.
fn game_distribution_theme(&self) -> GameDistributionThemeTableHandle<'_>;
}
impl GameDistributionThemeTableAccess for super::RemoteTables {
fn game_distribution_theme(&self) -> GameDistributionThemeTableHandle<'_> {
GameDistributionThemeTableHandle {
imp: self
.imp
.get_table::<GameDistributionTheme>("game_distribution_theme"),
ctx: std::marker::PhantomData,
}
}
}
pub struct GameDistributionThemeInsertCallbackId(__sdk::CallbackId);
pub struct GameDistributionThemeDeleteCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableLike for GameDistributionThemeTableHandle<'ctx> {
type Row = GameDistributionTheme;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = GameDistributionTheme> + '_ {
self.imp.iter()
}
}
impl<'ctx> __sdk::Table for GameDistributionThemeTableHandle<'ctx> {
type Row = GameDistributionTheme;
type EventContext = super::EventContext;
fn count(&self) -> u64 {
self.imp.count()
}
fn iter(&self) -> impl Iterator<Item = GameDistributionTheme> + '_ {
self.imp.iter()
}
type InsertCallbackId = GameDistributionThemeInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeInsertCallbackId {
GameDistributionThemeInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: GameDistributionThemeInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
type DeleteCallbackId = GameDistributionThemeDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeDeleteCallbackId {
GameDistributionThemeDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: GameDistributionThemeDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
impl<'ctx> __sdk::WithInsert for GameDistributionThemeTableHandle<'ctx> {
type InsertCallbackId = GameDistributionThemeInsertCallbackId;
fn on_insert(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeInsertCallbackId {
GameDistributionThemeInsertCallbackId(self.imp.on_insert(Box::new(callback)))
}
fn remove_on_insert(&self, callback: GameDistributionThemeInsertCallbackId) {
self.imp.remove_on_insert(callback.0)
}
}
impl<'ctx> __sdk::WithDelete for GameDistributionThemeTableHandle<'ctx> {
type DeleteCallbackId = GameDistributionThemeDeleteCallbackId;
fn on_delete(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
) -> GameDistributionThemeDeleteCallbackId {
GameDistributionThemeDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
}
fn remove_on_delete(&self, callback: GameDistributionThemeDeleteCallbackId) {
self.imp.remove_on_delete(callback.0)
}
}
pub struct GameDistributionThemeUpdateCallbackId(__sdk::CallbackId);
impl<'ctx> __sdk::TableWithPrimaryKey for GameDistributionThemeTableHandle<'ctx> {
type UpdateCallbackId = GameDistributionThemeUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> GameDistributionThemeUpdateCallbackId {
GameDistributionThemeUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: GameDistributionThemeUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
impl<'ctx> __sdk::WithUpdate for GameDistributionThemeTableHandle<'ctx> {
type UpdateCallbackId = GameDistributionThemeUpdateCallbackId;
fn on_update(
&self,
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
) -> GameDistributionThemeUpdateCallbackId {
GameDistributionThemeUpdateCallbackId(self.imp.on_update(Box::new(callback)))
}
fn remove_on_update(&self, callback: GameDistributionThemeUpdateCallbackId) {
self.imp.remove_on_update(callback.0)
}
}
/// Access to the `theme_id` unique index on the table `game_distribution_theme`,
/// which allows point queries on the field of the same name
/// via the [`GameDistributionThemeThemeIdUnique::find`] method.
///
/// Users are encouraged not to explicitly reference this type,
/// but to directly chain method calls,
/// like `ctx.db.game_distribution_theme().theme_id().find(...)`.
pub struct GameDistributionThemeThemeIdUnique<'ctx> {
imp: __sdk::UniqueConstraintHandle<GameDistributionTheme, String>,
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
}
impl<'ctx> GameDistributionThemeTableHandle<'ctx> {
/// Get a handle on the `theme_id` unique index on the table `game_distribution_theme`.
pub fn theme_id(&self) -> GameDistributionThemeThemeIdUnique<'ctx> {
GameDistributionThemeThemeIdUnique {
imp: self.imp.get_unique_constraint::<String>("theme_id"),
phantom: std::marker::PhantomData,
}
}
}
impl<'ctx> GameDistributionThemeThemeIdUnique<'ctx> {
/// Find the subscribed row whose `theme_id` column value is equal to `col_val`,
/// if such a row is present in the client cache.
pub fn find(&self, col_val: &String) -> Option<GameDistributionTheme> {
self.imp.find(col_val)
}
}
#[doc(hidden)]
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
let _table = client_cache.get_or_make_table::<GameDistributionTheme>("game_distribution_theme");
_table.add_unique_constraint::<String>("theme_id", |row| &row.theme_id);
}
#[doc(hidden)]
pub(super) fn parse_table_update(
raw_updates: __ws::v2::TableUpdate,
) -> __sdk::Result<__sdk::TableUpdate<GameDistributionTheme>> {
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
__sdk::InternalError::failed_parse("TableUpdate<GameDistributionTheme>", "TableUpdate")
.with_cause(e)
.into()
})
}
#[allow(non_camel_case_types)]
/// Extension trait for query builder access to the table `GameDistributionTheme`.
///
/// Implemented for [`__sdk::QueryTableAccessor`].
pub trait game_distribution_themeQueryTableAccess {
#[allow(non_snake_case)]
/// Get a query builder for the table `GameDistributionTheme`.
fn game_distribution_theme(&self) -> __sdk::__query_builder::Table<GameDistributionTheme>;
}
impl game_distribution_themeQueryTableAccess for __sdk::QueryTableAccessor {
fn game_distribution_theme(&self) -> __sdk::__query_builder::Table<GameDistributionTheme> {
__sdk::__query_builder::Table::new("game_distribution_theme")
}
}
@@ -0,0 +1,75 @@
// 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)]
pub struct GameDistributionTheme {
pub theme_id: String,
pub name: String,
pub summary: String,
pub badge: String,
pub sort_order: i64,
pub status: String,
pub created_by_user_id: String,
pub created_at: __sdk::Timestamp,
pub updated_at: __sdk::Timestamp,
}
impl __sdk::InModule for GameDistributionTheme {
type Module = super::RemoteModule;
}
/// Column accessor struct for the table `GameDistributionTheme`.
///
/// Provides typed access to columns for query building.
pub struct GameDistributionThemeCols {
pub theme_id: __sdk::__query_builder::Col<GameDistributionTheme, String>,
pub name: __sdk::__query_builder::Col<GameDistributionTheme, String>,
pub summary: __sdk::__query_builder::Col<GameDistributionTheme, String>,
pub badge: __sdk::__query_builder::Col<GameDistributionTheme, String>,
pub sort_order: __sdk::__query_builder::Col<GameDistributionTheme, i64>,
pub status: __sdk::__query_builder::Col<GameDistributionTheme, String>,
pub created_by_user_id: __sdk::__query_builder::Col<GameDistributionTheme, String>,
pub created_at: __sdk::__query_builder::Col<GameDistributionTheme, __sdk::Timestamp>,
pub updated_at: __sdk::__query_builder::Col<GameDistributionTheme, __sdk::Timestamp>,
}
impl __sdk::__query_builder::HasCols for GameDistributionTheme {
type Cols = GameDistributionThemeCols;
fn cols(table_name: &'static str) -> Self::Cols {
GameDistributionThemeCols {
theme_id: __sdk::__query_builder::Col::new(table_name, "theme_id"),
name: __sdk::__query_builder::Col::new(table_name, "name"),
summary: __sdk::__query_builder::Col::new(table_name, "summary"),
badge: __sdk::__query_builder::Col::new(table_name, "badge"),
sort_order: __sdk::__query_builder::Col::new(table_name, "sort_order"),
status: __sdk::__query_builder::Col::new(table_name, "status"),
created_by_user_id: __sdk::__query_builder::Col::new(table_name, "created_by_user_id"),
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
updated_at: __sdk::__query_builder::Col::new(table_name, "updated_at"),
}
}
}
/// Indexed column accessor struct for the table `GameDistributionTheme`.
///
/// Provides typed access to indexed columns for query building.
pub struct GameDistributionThemeIxCols {
pub created_at: __sdk::__query_builder::IxCol<GameDistributionTheme, __sdk::Timestamp>,
pub theme_id: __sdk::__query_builder::IxCol<GameDistributionTheme, String>,
}
impl __sdk::__query_builder::HasIxCols for GameDistributionTheme {
type IxCols = GameDistributionThemeIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
GameDistributionThemeIxCols {
created_at: __sdk::__query_builder::IxCol::new(table_name, "created_at"),
theme_id: __sdk::__query_builder::IxCol::new(table_name, "theme_id"),
}
}
}
impl __sdk::__query_builder::CanBeLookupTable for GameDistributionTheme {}
@@ -836,6 +836,76 @@ pub struct GameDistributionCollection {
pub(crate) created_at: Timestamp,
}
/// 平台 / 运营命名的「共创主题」:一棵(或一组)作品树的归组实体。
///
/// 主题不是作品的属性,而是**运营叙事**:作品在游戏 Tab 里仍作为独立作品展示,主题只是额外
/// 的归组维度(一个根可以同时属于多个主题)。归属关系在多对多的 `game_distribution_theme_member`
/// 里,因此 `game` 主行(详情页热路径)不挂任何运营字段。
///
/// `status` 三态 `draft` / `published` / `archived`:公开侧只出现 `published`(
/// `module_game_distribution::game_distribution_theme_public_visible`),归档不是删除——成员行
/// 保留,只是不再进入公开投影。`created_by_user_id` 只用于审计「谁建的」,不参与鉴权判定。
#[spacetimedb::table(
accessor = game_distribution_theme,
index(
accessor = by_game_distribution_theme_created_at,
btree(columns = [created_at])
),
)]
#[derive(Clone)]
pub struct GameDistributionTheme {
/// 平台签发的主题 ID,形如 `theme-*`;由服务端生成,不接受客户端指定。
#[primary_key]
pub(crate) theme_id: String,
/// 主题名(运营命名,不是根作品标题的派生)。
pub(crate) name: String,
pub(crate) summary: String,
/// 角标短文本(本轮用它 + 名称承担「封面」的呈现职责)。
pub(crate) badge: String,
/// 运营排序权重(成员排序使用)。
pub(crate) sort_order: i64,
/// `draft` / `published` / `archived`。
pub(crate) status: String,
/// 创建该主题的运营账号(= 后台会话主体),用于审计,不参与鉴权判定。
pub(crate) created_by_user_id: String,
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
}
/// 主题成员:只允许**根作品**(该作品没有血缘行,即第 0 代)。
///
/// 主键是确定性构造的 `{theme_id}:{root_game_id}`
/// (`module_game_distribution::game_distribution_theme_member_id`):同一主题内同一根在
/// **结构上**不可能出现第二行,去重由主键约束本身承担,而不是靠「事务里先查后写」。因此这里
/// **不需要** `(theme_id, root_game_id)` 唯一索引;两条具名索引只服务两个真实读取方向
/// (主题页按主题取成员、作品详情按根反查所属主题)。
///
/// 跨主题的同一根会有**多行**(每主题一行),这是预期行为而不是重复数据。作品下架 / 软删除
/// **不删除**成员行:读取时按当前公开性过滤(`game_distribution_theme_member_visible`),
/// 作品重新公开后成员自动回到主题页。`sort_order` 允许重复,排序由 `member_id` 兜底成全序。
#[spacetimedb::table(
accessor = game_distribution_theme_member,
index(
accessor = by_game_distribution_theme_member_theme_id,
btree(columns = [theme_id])
),
index(
accessor = by_game_distribution_theme_member_root_game_id,
btree(columns = [root_game_id])
),
)]
#[derive(Clone)]
pub struct GameDistributionThemeMember {
/// 确定性主键 `"{theme_id}:{root_game_id}"`(同 `game_distribution_collection` 的写法)。
#[primary_key]
pub(crate) member_id: String,
pub(crate) theme_id: String,
/// 只允许**根作品**。
pub(crate) root_game_id: String,
pub(crate) sort_order: i64,
pub(crate) created_at: Timestamp,
}
/// 游戏发行版本。包摘要、确认字节数和送审资料在创建后作为不可变快照保留;状态和
/// 审核/对象安装时间由后续事务推进。
#[spacetimedb::table(
@@ -225,6 +225,10 @@ macro_rules! migration_tables {
// 用户对公开作品的收藏(收录):真实用户态投影,随迁移导出/导入;
// 作品下架不删除行(读取时按当前公开性过滤,重新公开后自动回来)。
game_distribution_collection,
// 共创主题与成员:主题是运营命名实体、成员是「主题 ↔ 根作品」的运营归组事实,
// 随迁移导出/导入;归档 / 下架只改变公开投影,**不删除成员行**。
game_distribution_theme,
game_distribution_theme_member,
llm_router_account,
llm_router_billing_checkpoint,
agc_model_catalog,