Files
cozsweet-frontend-nextjs/src/stores/user/user-machine.actors.ts
T

102 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* User 状态机:Actors(异步服务)
*
* 从 `user-machine.ts` 抽出的"Actors"段:
* - `userInitActor`fromPromise:读 UserStorage 并行 user + avatarUrl
* - `userFetchActor`fromPromise:调 API + 持久化到本地)
* - `userLogoutActor`fromPromiseauth logout + 清本地数据)
*
* 设计:
* - 依赖 `user-machine.helpers.ts`toView, readInitData, userStorage
* - 仓库以接口类型注入(调用面只看接口,运行时仍是同一单例)
* - 命名约定:`[user-machine]` 前缀日志(如未来加)—— 与 chat 模块对齐
*/
import { fromPromise } from "xstate";
import { userRepository } from "@/data/repositories/user_repository";
import { authRepository } from "@/data/repositories/auth_repository";
import type {
IAuthRepository,
IUserRepository,
} from "@/data/repositories/interfaces";
import { Result } from "@/utils";
import {
applyEntitlementSnapshotToView,
type InitData,
readInitData,
toEntitlementSnapshot,
toView,
type UserFetchData,
userStorage,
} from "./user-machine.helpers";
// ============================================================
// 仓库注入
// ============================================================
// 仓库以接口类型注入:调用面只看接口,运行时仍是同一单例
const userRepo: IUserRepository = userRepository;
const authRepo: IAuthRepository = authRepository;
// ============================================================
// Init actor:从 UserStorage 读 user + avatarUrl
// ============================================================
/**
* 读本地 user + avatarUrl(并行)。
* - 失败/无数据 → 返回 null,不抛错(machine 走 onDone 回到 idlecontext 是 null
* - onDone 赋 currentUser + avatarUrl 到 context
*/
export const userInitActor = fromPromise<InitData>(async () => readInitData());
// ============================================================
// Fetch actor:调 API 拉当前用户 + 持久化
// ============================================================
/**
* 调 `userRepo.getCurrentUser + getEntitlements` 拉当前 user 与权益:
* - user 成功 → 转 UserView + 写本地(userStorage.setUser
* - entitlements 成功 → 提取 isVip/creditBalance 写本地轻量快照
* - 任一失败都不阻断另一方,machine 层负责保留已有 context
*/
export const userFetchActor = fromPromise<UserFetchData>(async () => {
const [userResult, entitlementsResult] = await Promise.all([
userRepo.getCurrentUser(),
userRepo.getEntitlements(),
]);
const snapshot = Result.isOk(entitlementsResult)
? toEntitlementSnapshot({
isVip: entitlementsResult.data.isVip,
creditBalance: entitlementsResult.data.creditBalance,
})
: null;
if (snapshot) {
await userStorage.setEntitlementSnapshot(snapshot);
}
if (Result.isErr(userResult)) {
return { user: null, snapshot };
}
const view = applyEntitlementSnapshotToView(
toView(userResult.data.toJson()),
snapshot,
);
// 持久化到本地
await userStorage.setUser(userResult.data.toJson());
return { user: view, snapshot };
});
// ============================================================
// Logout actor:后端登出 + 清本地用户数据
// ============================================================
/**
* 登出:
* - 调 `authRepo.logout` 让后端吊销 token
* - `userStorage.clearUserData` 清本地 user / avatar 等
* - onDone / onError 都跑 `clearUser` actionmachine 层)—— 失败也清本地
*/
export const userLogoutActor = fromPromise<void>(async () => {
await authRepo.logout();
});