chore(契约): 三池余额 DTO 交给 ts-rs 生成,同步 SpacetimeDB 绑定

- 给 ProfileMudPointBalanceResponse 加 ts-bindings 特性下的 ts_rs::TS 导出,u64 标 ts(type = "number") 避免前端分叉成 bigint
- 新增 packages/shared/src/contracts/generated/ProfileMudPointBalanceResponse.ts,runtime.ts 改为 re-export 不再手写第二份字段
- 新增 packages/shared/src/utils/mudPoints.ts:派生展示用合计与北京时间月度刷新文案
- 同步 spacetime-client 生成绑定:升级报价改名为 snapshot,与 module 侧改动对齐

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-10-02 19:00:08 +08:00
parent c772600d59
commit abc92d230b
8 changed files with 163 additions and 40 deletions
@@ -0,0 +1,19 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ProfileMudPointBalanceResponse = {
/**
* 每日免费泥点:北京业务日有效,跨日清零并重置。
*/
dailyFreePoints: number;
dailyFreeResetPoints: number;
dailyFreeResetsAt: string;
/**
* 月度泥点:会员每期额度,到刷新时刻清零再发新额度。
*/
monthlyPoints: number;
monthlyResetsAt: string | null;
/**
* 永久泥点:不过期,创作照常扣点。
*/
permanentPoints: number;
};
+49 -34
View File
@@ -1,4 +1,7 @@
import type { JsonObject } from './common';
import type { ProfileMudPointBalanceResponse as ProfileMudPointBalance } from './generated/ProfileMudPointBalanceResponse';
export type { ProfileMudPointBalance };
export const SAVE_SNAPSHOT_VERSION = 2;
export const DEFAULT_MUSIC_VOLUME = 0.42;
@@ -118,15 +121,17 @@ export type ExternalApiKeyMutationResponse = {
export type ProfileRechargeProductKind = 'points' | 'membership';
export type ProfileMembershipStatus = 'normal' | 'active';
export type ProfileMembershipTier =
/** 会员档位:`normal` 是非会员占位档位,不可购买。 */
export type ProfileMembershipPlan =
| 'normal'
| 'month'
| 'season'
| 'year'
| 'starter'
| 'basic'
| 'plus'
| 'pro'
| 'ultimate';
| 'max';
/** 会员周期:有效期内不支持月年互换,到期后重新选择。 */
export type ProfileMembershipCycleKind = 'monthly' | 'yearly';
/** 模型权限:`basic` 仅基础模型,`full` 基础及高性能模型。 */
export type ProfileMembershipModelAccess = 'basic' | 'full';
export type ProfileRechargeOrderStatus =
| 'pending'
| 'paid'
@@ -145,28 +150,27 @@ export type ProfileRechargeProduct = {
durationDays: number;
badgeLabel: string;
description: string;
tier: ProfileMembershipTier;
membershipPeriodPoints: number;
membershipPeriodDays: number;
membershipQueueLimit: number;
membershipDiscountBps: number;
};
export type ProfileMembershipBenefit = {
benefitName: string;
normalValue: string;
monthValue: string;
seasonValue: string;
yearValue: string;
starterValue: string;
basicValue: string;
proValue: string;
ultimateValue: string;
/** 会员档位目录行;价格与权益的唯一真相源是后端 `profile_membership_plan` 表。 */
export type ProfileMembershipPlanRecord = {
plan: ProfileMembershipPlan;
title: string;
rank: number;
monthPriceCents: number;
yearPriceCents: number;
periodPoints: number;
modelAccess: ProfileMembershipModelAccess;
/** 并发上限;`128` 表示不设上限。 */
concurrentJobLimit: number;
sortOrder: number;
enabled: boolean;
};
export type ProfileMembership = {
status: ProfileMembershipStatus;
tier: ProfileMembershipTier;
/** 当前档位;非会员与已到期都是 `normal`。 */
plan: ProfileMembershipPlan;
startedAt: string | null;
expiresAt: string | null;
updatedAt: string | null;
@@ -174,17 +178,29 @@ export type ProfileMembership = {
cycleResetsAt: string | null;
cycleGrantedPoints: number;
cycleRemainingPoints: number;
cyclePeriodDays: number;
/** 当前期序号(1-based)。 */
cycleIndex: number;
/** 本次购买总期数(月付 1 / 年付 12)。 */
cycleCount: number;
cycleKind: ProfileMembershipCycleKind;
};
export type ProfileMudPointBalance = {
totalPoints: number;
permanentPoints: number;
limitedPoints: number;
limitedExpiresAt: string | null;
dailyFreePoints: number;
dailyFreeResetPoints: number;
dailyFreeResetsAt: string;
/** 补差升级报价;下单时后端会用同一套规则重算,前端展示的数字不是真相源。 */
export type ProfileMembershipUpgradeQuote = {
currentPlan: ProfileMembershipPlan;
targetPlan: ProfileMembershipPlan;
cycleKind: ProfileMembershipCycleKind;
cycleIndex: number;
cycleCount: number;
/** 应付金额(分)。 */
amountCents: number;
/** 本期补点。 */
grantedPointsDelta: number;
/** 变更后月度余额 = 原剩余月度余额 + 补点。 */
monthlyBalanceAfter: number;
expiresAt: string | null;
cycleResetsAt: string | null;
remainingFullMonths: number;
};
export type ProfileRechargeOrder = {
@@ -207,13 +223,12 @@ export type ProfileRechargeOrder = {
};
export type ProfileRechargeCenterResponse = {
walletBalance: number;
// TODO mirror backend: should not nullable
mudPointBalance?: ProfileMudPointBalance;
membership: ProfileMembership;
pointProducts: ProfileRechargeProduct[];
membershipProducts: ProfileRechargeProduct[];
benefits: ProfileMembershipBenefit[];
/** 会员档位目录(后台可改价);会员购买 / 升级的价格与权益以它为准。 */
membershipPlans: ProfileMembershipPlanRecord[];
latestOrder: ProfileRechargeOrder | null;
hasPointsRecharged: boolean;
dailyFreePoints?: ProfileDailyFreePoints;
+1
View File
@@ -12,4 +12,5 @@ export * from './http';
export * from './llm/narrativeLanguage';
export * from './llm/parsers';
export * from './stores/createProfileWalletStore';
export * from './utils/mudPoints';
export * from './utils/signedReadUrlCache';
+73
View File
@@ -0,0 +1,73 @@
import type { ProfileMudPointBalance } from '../contracts/runtime';
/**
* 泥点余额的展示视图。
*
* 后端只并列暴露三个池子(每日免费 / 月度 / 永久),**不**提供「可用总额」字段,
* 以「合计」与「可用」的口径散回后端或各自相加都会分叉。需要合计展示时统一走
* {@link resolveProfileMudPointBalanceView}。
*/
export type ProfileMudPointBalanceView = ProfileMudPointBalance & {
/** 每日免费 + 月度 + 永久;只用于展示,不代表「可用额度」。 */
totalPoints: number;
};
export function resolveProfileMudPointBalanceView(
balance: ProfileMudPointBalance,
): ProfileMudPointBalanceView {
// 三个池子按 0 兜底:接口缺字段时宁可显示 0,也不能让合计变成 NaN。
return {
...balance,
dailyFreePoints: balance.dailyFreePoints ?? 0,
monthlyPoints: balance.monthlyPoints ?? 0,
permanentPoints: balance.permanentPoints ?? 0,
totalPoints:
(balance.dailyFreePoints ?? 0) +
(balance.monthlyPoints ?? 0) +
(balance.permanentPoints ?? 0),
};
}
/**
* 从任意余额形状推导展示用合计;三池缺省按 0 处理,整体为 `undefined` 时返回 `undefined`。
*/
export function resolveProfileMudPointDisplayTotal(
balance: ProfileMudPointBalance | null | undefined,
): number | undefined {
if (!balance) {
return undefined;
}
return resolveProfileMudPointBalanceView(balance).totalPoints;
}
/**
* 月度泥点的刷新时刻展示文案。
*
* 账期锚点按北京时间定义(见 ADR「会员账期按开通日自然月」),所以展示也固定用北京时间,
* 不随设备时区漂移;无会员周期或时间非法时返回 `null`,由调用方给兜底说明。
*/
export function formatMudPointResetAt(
value: string | null | undefined,
): string | null {
if (!value) {
return null;
}
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) {
return null;
}
const parts = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
}).formatToParts(parsed);
const lookup = (type: Intl.DateTimeFormatPartTypes) =>
parts.find((part) => part.type === type)?.value ?? '00';
return `${lookup('year')}-${lookup('month')}-${lookup('day')} ${lookup('hour')}:${lookup(
'minute',
)}`;
}
@@ -201,15 +201,30 @@ pub struct ProfileDailyFreePointsResponse {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "ts-bindings", derive(ts_rs::TS))]
#[cfg_attr(
feature = "ts-bindings",
ts(
export,
export_to = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../../packages/shared/src/contracts/generated/"
)
)
)]
pub struct ProfileMudPointBalanceResponse {
/// 每日免费泥点:北京业务日有效,跨日清零并重置。
#[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
pub daily_free_points: u64,
#[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
pub daily_free_reset_points: u64,
pub daily_free_resets_at: String,
/// 月度泥点:会员每期额度,到刷新时刻清零再发新额度。
#[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
pub monthly_points: u64,
pub monthly_resets_at: Option<String>,
/// 永久泥点:不过期,创作照常扣点。
#[cfg_attr(feature = "ts-bindings", ts(type = "number"))]
pub permanent_points: u64,
}
@@ -665,7 +665,7 @@ pub mod runtime_profile_membership_snapshot_type;
pub mod runtime_profile_membership_status_type;
pub mod runtime_profile_membership_upgrade_quote_get_input_type;
pub mod runtime_profile_membership_upgrade_quote_procedure_result_type;
pub mod runtime_profile_membership_upgrade_quote_record_type;
pub mod runtime_profile_membership_upgrade_quote_snapshot_type;
pub mod runtime_profile_recharge_center_get_input_type;
pub mod runtime_profile_recharge_center_procedure_result_type;
pub mod runtime_profile_recharge_center_snapshot_type;
@@ -1482,7 +1482,7 @@ pub use runtime_profile_membership_snapshot_type::RuntimeProfileMembershipSnapsh
pub use runtime_profile_membership_status_type::RuntimeProfileMembershipStatus;
pub use runtime_profile_membership_upgrade_quote_get_input_type::RuntimeProfileMembershipUpgradeQuoteGetInput;
pub use runtime_profile_membership_upgrade_quote_procedure_result_type::RuntimeProfileMembershipUpgradeQuoteProcedureResult;
pub use runtime_profile_membership_upgrade_quote_record_type::RuntimeProfileMembershipUpgradeQuoteRecord;
pub use runtime_profile_membership_upgrade_quote_snapshot_type::RuntimeProfileMembershipUpgradeQuoteSnapshot;
pub use runtime_profile_recharge_center_get_input_type::RuntimeProfileRechargeCenterGetInput;
pub use runtime_profile_recharge_center_procedure_result_type::RuntimeProfileRechargeCenterProcedureResult;
pub use runtime_profile_recharge_center_snapshot_type::RuntimeProfileRechargeCenterSnapshot;
@@ -4,13 +4,13 @@
#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
use super::runtime_profile_membership_upgrade_quote_record_type::RuntimeProfileMembershipUpgradeQuoteRecord;
use super::runtime_profile_membership_upgrade_quote_snapshot_type::RuntimeProfileMembershipUpgradeQuoteSnapshot;
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub struct RuntimeProfileMembershipUpgradeQuoteProcedureResult {
pub ok: bool,
pub quote: Option<RuntimeProfileMembershipUpgradeQuoteRecord>,
pub quote: Option<RuntimeProfileMembershipUpgradeQuoteSnapshot>,
pub error_message: Option<String>,
}
@@ -6,7 +6,7 @@ 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 RuntimeProfileMembershipUpgradeQuoteRecord {
pub struct RuntimeProfileMembershipUpgradeQuoteSnapshot {
pub current_plan: String,
pub target_plan: String,
pub cycle_kind: String,
@@ -20,6 +20,6 @@ pub struct RuntimeProfileMembershipUpgradeQuoteRecord {
pub remaining_full_months: u32,
}
impl __sdk::InModule for RuntimeProfileMembershipUpgradeQuoteRecord {
impl __sdk::InModule for RuntimeProfileMembershipUpgradeQuoteSnapshot {
type Module = super::RemoteModule;
}