refactor(data): move Result utility to utils and simplify API
Relocate the Result type from `@/data/result` to `@/utils/result` and replace the discriminated-union API (`kind`/`value`) with a simpler boolean-based API (`success`/`data`) across all repositories and storage classes for improved readability and consistency.
This commit is contained in:
@@ -26,7 +26,7 @@ import { RefreshTokenResponse } from "@/data/dto/auth/refresh_token_response";
|
|||||||
import { RegisterRequest } from "@/data/dto/auth/register_request";
|
import { RegisterRequest } from "@/data/dto/auth/register_request";
|
||||||
import { SendCodeRequest } from "@/data/dto/auth/send_code_request";
|
import { SendCodeRequest } from "@/data/dto/auth/send_code_request";
|
||||||
import { User } from "@/data/dto/user/user";
|
import { User } from "@/data/dto/user/user";
|
||||||
import { Result } from "@/data/result";
|
import { Result } from "@/utils/result";
|
||||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||||
import type { IAuthStorage } from "@/data/storage/auth/iauth_storage";
|
import type { IAuthStorage } from "@/data/storage/auth/iauth_storage";
|
||||||
import { UserStorage } from "@/data/storage/user/user_storage";
|
import { UserStorage } from "@/data/storage/user/user_storage";
|
||||||
@@ -204,10 +204,10 @@ export class AuthRepository {
|
|||||||
*/
|
*/
|
||||||
async refreshToken(): Promise<Result<RefreshTokenResponse>> {
|
async refreshToken(): Promise<Result<RefreshTokenResponse>> {
|
||||||
const existing = await this.storage.getRefreshToken();
|
const existing = await this.storage.getRefreshToken();
|
||||||
if (existing.kind !== "success" || !existing.value) {
|
if (!existing.success || !existing.data) {
|
||||||
return Result.err(new Error("No refresh token available"));
|
return Result.err(new Error("No refresh token available"));
|
||||||
}
|
}
|
||||||
const refreshToken = existing.value;
|
const refreshToken = existing.data;
|
||||||
return Result.wrap(async () => {
|
return Result.wrap(async () => {
|
||||||
const response = await this.api.refreshToken(
|
const response = await this.api.refreshToken(
|
||||||
RefreshTokenRequest.from({ refreshToken }),
|
RefreshTokenRequest.from({ refreshToken }),
|
||||||
@@ -228,7 +228,7 @@ export class AuthRepository {
|
|||||||
return Result.wrap(async () => {
|
return Result.wrap(async () => {
|
||||||
const user = await this.api.getCurrentUser();
|
const user = await this.api.getCurrentUser();
|
||||||
const writeResult = await this.userStorage.setUser(user.toJson());
|
const writeResult = await this.userStorage.setUser(user.toJson());
|
||||||
if (writeResult.kind === "failure") {
|
if (!writeResult.success) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[AuthRepository] failed to cache current user",
|
"[AuthRepository] failed to cache current user",
|
||||||
writeResult.error,
|
writeResult.error,
|
||||||
@@ -261,21 +261,21 @@ export class AuthRepository {
|
|||||||
*/
|
*/
|
||||||
private async _saveLoginData(data: LoginResponse): Promise<void> {
|
private async _saveLoginData(data: LoginResponse): Promise<void> {
|
||||||
const r1 = await this.storage.setLoginToken(data.token);
|
const r1 = await this.storage.setLoginToken(data.token);
|
||||||
if (r1.kind === "failure") {
|
if (!r1.success) {
|
||||||
console.warn("[AuthRepository] setLoginToken failed", r1.error);
|
console.warn("[AuthRepository] setLoginToken failed", r1.error);
|
||||||
}
|
}
|
||||||
if (data.refreshToken) {
|
if (data.refreshToken) {
|
||||||
const r2 = await this.storage.setRefreshToken(data.refreshToken);
|
const r2 = await this.storage.setRefreshToken(data.refreshToken);
|
||||||
if (r2.kind === "failure") {
|
if (!r2.success) {
|
||||||
console.warn("[AuthRepository] setRefreshToken failed", r2.error);
|
console.warn("[AuthRepository] setRefreshToken failed", r2.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const r3 = await this.userStorage.setUser(data.user.toJson());
|
const r3 = await this.userStorage.setUser(data.user.toJson());
|
||||||
if (r3.kind === "failure") {
|
if (!r3.success) {
|
||||||
console.warn("[AuthRepository] setUser failed", r3.error);
|
console.warn("[AuthRepository] setUser failed", r3.error);
|
||||||
}
|
}
|
||||||
const r4 = await this.userStorage.setUserId(data.user.id);
|
const r4 = await this.userStorage.setUserId(data.user.id);
|
||||||
if (r4.kind === "failure") {
|
if (!r4.success) {
|
||||||
console.warn("[AuthRepository] setUserId failed", r4.error);
|
console.warn("[AuthRepository] setUserId failed", r4.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { ChatHistoryResponse } from "@/data/dto/chat/chat_history_response";
|
|||||||
import { ChatMessage } from "@/data/dto/chat/chat_message";
|
import { ChatMessage } from "@/data/dto/chat/chat_message";
|
||||||
import { ChatSendResponse } from "@/data/dto/chat/chat_send_response";
|
import { ChatSendResponse } from "@/data/dto/chat/chat_send_response";
|
||||||
import { SendMessageRequest } from "@/data/dto/chat/send_message_request";
|
import { SendMessageRequest } from "@/data/dto/chat/send_message_request";
|
||||||
import { Result } from "@/data/result";
|
import { Result } from "@/utils/result";
|
||||||
import { LocalChatStorage } from "@/data/storage/chat/local_chat_storage";
|
import { LocalChatStorage } from "@/data/storage/chat/local_chat_storage";
|
||||||
import { LocalMessage } from "@/data/storage/chat/local_message";
|
import { LocalMessage } from "@/data/storage/chat/local_message";
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export class ChatRepository {
|
|||||||
messages: readonly ChatMessage[],
|
messages: readonly ChatMessage[],
|
||||||
): Promise<Result<void>> {
|
): Promise<Result<void>> {
|
||||||
const cleared = await this.localStorage.clearAll();
|
const cleared = await this.localStorage.clearAll();
|
||||||
if (cleared.kind === "failure") {
|
if (!cleared.success) {
|
||||||
return Result.err(cleared.error);
|
return Result.err(cleared.error);
|
||||||
}
|
}
|
||||||
return this._mapLocalStorageResult(
|
return this._mapLocalStorageResult(
|
||||||
@@ -83,10 +83,10 @@ export class ChatRepository {
|
|||||||
/** 读取所有本地消息,按 dbId 升序。 */
|
/** 读取所有本地消息,按 dbId 升序。 */
|
||||||
async getLocalMessages(): Promise<Result<ChatMessage[]>> {
|
async getLocalMessages(): Promise<Result<ChatMessage[]>> {
|
||||||
const result = await this.localStorage.getAllMessages();
|
const result = await this.localStorage.getAllMessages();
|
||||||
if (result.kind === "failure") {
|
if (!result.success) {
|
||||||
return Result.err(result.error);
|
return Result.err(result.error);
|
||||||
}
|
}
|
||||||
return Result.ok(result.value.map((lm) => this._localToChat(lm)));
|
return Result.ok(result.data.map((lm) => this._localToChat(lm)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 清空本地消息。 */
|
/** 清空本地消息。 */
|
||||||
@@ -126,20 +126,14 @@ export class ChatRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把 storage 层 `{kind, value/error}` 形态的 `Result<T>` 桥接到仓库层
|
* 把 storage 层 `Result<T>` 透传:仓库与 storage 现在共享同一全局
|
||||||
* `{success, data/error}` 形态。错误统一用 `Error` 包装。
|
* `Result<T>`(来自 `@/utils/result`),不需要任何形状转换。
|
||||||
|
* 该方法保留仅为未来万一又出现差异化时方便快速插入适配。
|
||||||
*/
|
*/
|
||||||
private async _mapLocalStorageResult<T>(
|
private async _mapLocalStorageResult<T>(
|
||||||
promise: Promise<
|
promise: Promise<Result<T>>,
|
||||||
| { readonly kind: "success"; readonly value: T }
|
|
||||||
| { readonly kind: "failure"; readonly error: unknown }
|
|
||||||
>,
|
|
||||||
): Promise<Result<T>> {
|
): Promise<Result<T>> {
|
||||||
const r = await promise;
|
return await promise;
|
||||||
if (r.kind === "success") {
|
|
||||||
return Result.ok(r.value);
|
|
||||||
}
|
|
||||||
return Result.err(r.error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import { MetricsApi, metricsApi } from "@/data/api";
|
import { MetricsApi, metricsApi } from "@/data/api";
|
||||||
import { AppEvent } from "@/data/dto/metrics/app_event";
|
import { AppEvent } from "@/data/dto/metrics/app_event";
|
||||||
import { PwaEvent } from "@/data/dto/metrics/pwa_event";
|
import { PwaEvent } from "@/data/dto/metrics/pwa_event";
|
||||||
import { Result } from "@/data/result";
|
import { Result } from "@/utils/result";
|
||||||
|
|
||||||
export class MetricsRepository {
|
export class MetricsRepository {
|
||||||
constructor(private readonly api: MetricsApi) {}
|
constructor(private readonly api: MetricsApi) {}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { CreditsHistoryData } from "@/data/dto/user/credits_history_data";
|
|||||||
import { UpdateProfileRequest } from "@/data/dto/user/update_profile_request";
|
import { UpdateProfileRequest } from "@/data/dto/user/update_profile_request";
|
||||||
import { User } from "@/data/dto/user/user";
|
import { User } from "@/data/dto/user/user";
|
||||||
import { UserStatsResponse } from "@/data/dto/user/user_stats_response";
|
import { UserStatsResponse } from "@/data/dto/user/user_stats_response";
|
||||||
import { Result } from "@/data/result";
|
import { Result } from "@/utils/result";
|
||||||
|
|
||||||
export class UserRepository {
|
export class UserRepository {
|
||||||
constructor(private readonly api: UserApi) {}
|
constructor(private readonly api: UserApi) {}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LocalStorage } from "../local_storage";
|
import { LocalStorage } from "../local_storage";
|
||||||
import { Result, type Result as ResultT } from "../result";
|
import { Result, type Result as ResultT } from "@/utils/result";
|
||||||
import { StorageKeys } from "../storage_keys";
|
import { StorageKeys } from "../storage_keys";
|
||||||
|
|
||||||
export class AppStorage {
|
export class AppStorage {
|
||||||
@@ -78,7 +78,7 @@ export class AppStorage {
|
|||||||
todayString: string,
|
todayString: string,
|
||||||
): Promise<ResultT<boolean>> {
|
): Promise<ResultT<boolean>> {
|
||||||
const r = await AppStorage.ls.getString(key);
|
const r = await AppStorage.ls.getString(key);
|
||||||
if (r.kind === "failure") return r;
|
if (!r.success) return r;
|
||||||
return Result.success(r.value === null || r.value !== todayString);
|
return Result.ok(r.data === null || r.data !== todayString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LocalStorage } from "../local_storage";
|
import { LocalStorage } from "../local_storage";
|
||||||
import { Result, type Result as ResultT } from "../result";
|
import { Result, type Result as ResultT } from "@/utils/result";
|
||||||
import { StorageKeys } from "../storage_keys";
|
import { StorageKeys } from "../storage_keys";
|
||||||
import type { IAuthStorage } from "./iauth_storage";
|
import type { IAuthStorage } from "./iauth_storage";
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ export class AuthStorage implements IAuthStorage {
|
|||||||
}
|
}
|
||||||
async hasLoginToken(): Promise<ResultT<boolean>> {
|
async hasLoginToken(): Promise<ResultT<boolean>> {
|
||||||
const r = await this.getLoginToken();
|
const r = await this.getLoginToken();
|
||||||
if (r.kind === "failure") return Result.failure(r.error);
|
if (!r.success) return Result.err(r.error);
|
||||||
return Result.success(r.value !== null && r.value.length > 0);
|
return Result.ok(r.data !== null && r.data.length > 0);
|
||||||
}
|
}
|
||||||
clearLoginToken(): Promise<ResultT<void>> {
|
clearLoginToken(): Promise<ResultT<void>> {
|
||||||
return this.ls.remove(StorageKeys.loginToken);
|
return this.ls.remove(StorageKeys.loginToken);
|
||||||
@@ -64,8 +64,8 @@ export class AuthStorage implements IAuthStorage {
|
|||||||
}
|
}
|
||||||
async hasGuestToken(): Promise<ResultT<boolean>> {
|
async hasGuestToken(): Promise<ResultT<boolean>> {
|
||||||
const r = await this.getGuestToken();
|
const r = await this.getGuestToken();
|
||||||
if (r.kind === "failure") return Result.failure(r.error);
|
if (!r.success) return Result.err(r.error);
|
||||||
return Result.success(r.value !== null && r.value.length > 0);
|
return Result.ok(r.data !== null && r.data.length > 0);
|
||||||
}
|
}
|
||||||
clearGuestToken(): Promise<ResultT<void>> {
|
clearGuestToken(): Promise<ResultT<void>> {
|
||||||
return this.ls.remove(StorageKeys.guestToken);
|
return this.ls.remove(StorageKeys.guestToken);
|
||||||
@@ -107,9 +107,9 @@ export class AuthStorage implements IAuthStorage {
|
|||||||
|
|
||||||
async clearAuthData(): Promise<ResultT<void>> {
|
async clearAuthData(): Promise<ResultT<void>> {
|
||||||
const r1 = await this.clearLoginToken();
|
const r1 = await this.clearLoginToken();
|
||||||
if (r1.kind === "failure") return r1;
|
if (!r1.success) return r1;
|
||||||
const r2 = await this.clearGuestToken();
|
const r2 = await this.clearGuestToken();
|
||||||
if (r2.kind === "failure") return r2;
|
if (!r2.success) return r2;
|
||||||
return this.clearRefreshToken();
|
return this.clearRefreshToken();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* - 所有方法返回 `Promise<Result<T>>`,与 Dart `Future<Result<T>>` 对齐
|
* - 所有方法返回 `Promise<Result<T>>`,与 Dart `Future<Result<T>>` 对齐
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Result } from "../result";
|
import type { Result } from "@/utils/result";
|
||||||
|
|
||||||
export interface IAuthStorage {
|
export interface IAuthStorage {
|
||||||
getLoginToken(): Promise<Result<string | null>>;
|
getLoginToken(): Promise<Result<string | null>>;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { LocalStorage } from "../local_storage";
|
import { LocalStorage } from "../local_storage";
|
||||||
import { type Result as ResultT } from "../result";
|
import { type Result as ResultT } from "@/utils/result";
|
||||||
import { StorageKeys } from "../storage_keys";
|
import { StorageKeys } from "../storage_keys";
|
||||||
import type { IChatStorage } from "./ichat_storage";
|
import type { IChatStorage } from "./ichat_storage";
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* 跨天重置逻辑(`needsReset`)保留在 `GuestChatQuota` 类上,存储层只做读写。
|
* 跨天重置逻辑(`needsReset`)保留在 `GuestChatQuota` 类上,存储层只做读写。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Result } from "../result";
|
import type { Result } from "@/utils/result";
|
||||||
import type { GuestChatQuotaData } from "@/data/schemas/chat/guest_chat_quota";
|
import type { GuestChatQuotaData } from "@/data/schemas/chat/guest_chat_quota";
|
||||||
|
|
||||||
export interface IChatStorage {
|
export interface IChatStorage {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* 数据量大时考虑升级 schema 加索引。
|
* 数据量大时考虑升级 schema 加索引。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Result, type Result as ResultT } from "../result";
|
import { Result, type Result as ResultT } from "@/utils/result";
|
||||||
import { LocalChatDB } from "./local_chat_db";
|
import { LocalChatDB } from "./local_chat_db";
|
||||||
import { LocalMessage } from "./local_message";
|
import { LocalMessage } from "./local_message";
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@ export class LocalChatStorage {
|
|||||||
async init(): Promise<ResultT<void>> {
|
async init(): Promise<ResultT<void>> {
|
||||||
try {
|
try {
|
||||||
await this.db.open();
|
await this.db.open();
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +58,9 @@ export class LocalChatStorage {
|
|||||||
async saveMessage(message: LocalMessage): Promise<ResultT<void>> {
|
async saveMessage(message: LocalMessage): Promise<ResultT<void>> {
|
||||||
try {
|
try {
|
||||||
await this.db.messages.add(message.toRow());
|
await this.db.messages.add(message.toRow());
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +75,9 @@ export class LocalChatStorage {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,17 +88,17 @@ export class LocalChatStorage {
|
|||||||
const rows = await this.db.messages.toArray();
|
const rows = await this.db.messages.toArray();
|
||||||
// 按 dbId 升序(与 Dart `box.values.toList()` 插入序语义一致)
|
// 按 dbId 升序(与 Dart `box.values.toList()` 插入序语义一致)
|
||||||
rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0));
|
rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0));
|
||||||
return Result.success(rows.map((r) => LocalMessage.fromRow(r)));
|
return Result.ok(rows.map((r) => LocalMessage.fromRow(r)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMessageCount(): Promise<ResultT<number>> {
|
async getMessageCount(): Promise<ResultT<number>> {
|
||||||
try {
|
try {
|
||||||
return Result.success(await this.db.messages.count());
|
return Result.ok(await this.db.messages.count());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +110,9 @@ export class LocalChatStorage {
|
|||||||
.filter((r) => r.sessionId === sessionId)
|
.filter((r) => r.sessionId === sessionId)
|
||||||
.toArray();
|
.toArray();
|
||||||
rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0));
|
rows.sort((a, b) => (a.dbId ?? 0) - (b.dbId ?? 0));
|
||||||
return Result.success(rows.map((r) => LocalMessage.fromRow(r)));
|
return Result.ok(rows.map((r) => LocalMessage.fromRow(r)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +121,9 @@ export class LocalChatStorage {
|
|||||||
async clearAll(): Promise<ResultT<void>> {
|
async clearAll(): Promise<ResultT<void>> {
|
||||||
try {
|
try {
|
||||||
await this.db.messages.clear();
|
await this.db.messages.clear();
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,30 +134,30 @@ export class LocalChatStorage {
|
|||||||
async deleteMessage(index: number): Promise<ResultT<void>> {
|
async deleteMessage(index: number): Promise<ResultT<void>> {
|
||||||
try {
|
try {
|
||||||
if (!Number.isInteger(index) || index < 0) {
|
if (!Number.isInteger(index) || index < 0) {
|
||||||
return Result.failure(
|
return Result.err(
|
||||||
new RangeError(`deleteMessage: index ${index} out of range`),
|
new RangeError(`deleteMessage: index ${index} out of range`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const rows = await this.db.messages.toArray();
|
const rows = await this.db.messages.toArray();
|
||||||
if (index >= rows.length) {
|
if (index >= rows.length) {
|
||||||
return Result.failure(
|
return Result.err(
|
||||||
new RangeError(`deleteMessage: index ${index} out of range`),
|
new RangeError(`deleteMessage: index ${index} out of range`),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const target = rows[index]!;
|
const target = rows[index]!;
|
||||||
await this.db.messages.delete(target.dbId!);
|
await this.db.messages.delete(target.dbId!);
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<ResultT<void>> {
|
async close(): Promise<ResultT<void>> {
|
||||||
try {
|
try {
|
||||||
this.db.close();
|
this.db.close();
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
export * from "./auth_storage";
|
export * from "./auth_storage";
|
||||||
export * from "./local_storage";
|
export * from "./local_storage";
|
||||||
export * from "./result";
|
export * from "@/utils/result";
|
||||||
export * from "./storage_keys";
|
export * from "./storage_keys";
|
||||||
export * from "./app/app_storage";
|
export * from "./app/app_storage";
|
||||||
export * from "./auth/auth_storage";
|
export * from "./auth/auth_storage";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type ZodType } from "zod";
|
import { type ZodType } from "zod";
|
||||||
import { Result, type Result as ResultT } from "./result";
|
import { Result, type Result as ResultT } from "@/utils/result";
|
||||||
|
|
||||||
const SSR_ERROR_MSG =
|
const SSR_ERROR_MSG =
|
||||||
"localStorage is not available in this environment (SSR or non-browser)";
|
"localStorage is not available in this environment (SSR or non-browser)";
|
||||||
@@ -52,38 +52,38 @@ export class LocalStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getString(key: string): Promise<ResultT<string | null>> {
|
async getString(key: string): Promise<ResultT<string | null>> {
|
||||||
if (!this.isAvailable()) return Result.failure(new Error(SSR_ERROR_MSG));
|
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
|
||||||
try {
|
try {
|
||||||
const v = this.storage!.getItem(key);
|
const v = this.storage!.getItem(key);
|
||||||
if (v === null || v === "") return Result.success(null);
|
if (v === null || v === "") return Result.ok(null);
|
||||||
return Result.success(v);
|
return Result.ok(v);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setString(key: string, value: string): Promise<ResultT<void>> {
|
async setString(key: string, value: string): Promise<ResultT<void>> {
|
||||||
if (!this.isAvailable()) return Result.failure(new Error(SSR_ERROR_MSG));
|
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
|
||||||
try {
|
try {
|
||||||
this.storage!.setItem(key, value);
|
this.storage!.setItem(key, value);
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getJson<T>(key: string, schema: ZodType<T>): Promise<ResultT<T | null>> {
|
async getJson<T>(key: string, schema: ZodType<T>): Promise<ResultT<T | null>> {
|
||||||
const r = await this.getString(key);
|
const r = await this.getString(key);
|
||||||
if (r.kind === "failure") return Result.failure(r.error);
|
if (!r.success) return Result.err(r.error);
|
||||||
if (r.value === null) {
|
if (r.data === null) {
|
||||||
const value: T | null = null;
|
const value: T | null = null;
|
||||||
return Result.success(value);
|
return Result.ok(value);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = schema.parse(JSON.parse(r.value));
|
const parsed = schema.parse(JSON.parse(r.data));
|
||||||
return Result.success(parsed);
|
return Result.ok(parsed);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,32 +92,32 @@ export class LocalStorage {
|
|||||||
value: T,
|
value: T,
|
||||||
schema?: ZodType<T>,
|
schema?: ZodType<T>,
|
||||||
): Promise<ResultT<void>> {
|
): Promise<ResultT<void>> {
|
||||||
if (!this.isAvailable()) return Result.failure(new Error(SSR_ERROR_MSG));
|
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
|
||||||
try {
|
try {
|
||||||
const validated = schema ? schema.parse(value) : value;
|
const validated = schema ? schema.parse(value) : value;
|
||||||
this.storage!.setItem(key, JSON.stringify(validated));
|
this.storage!.setItem(key, JSON.stringify(validated));
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(key: string): Promise<ResultT<void>> {
|
async remove(key: string): Promise<ResultT<void>> {
|
||||||
if (!this.isAvailable()) return Result.failure(new Error(SSR_ERROR_MSG));
|
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
|
||||||
try {
|
try {
|
||||||
this.storage!.removeItem(key);
|
this.storage!.removeItem(key);
|
||||||
return Result.success(undefined);
|
return Result.ok(undefined);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async has(key: string): Promise<ResultT<boolean>> {
|
async has(key: string): Promise<ResultT<boolean>> {
|
||||||
if (!this.isAvailable()) return Result.failure(new Error(SSR_ERROR_MSG));
|
if (!this.isAvailable()) return Result.err(new Error(SSR_ERROR_MSG));
|
||||||
try {
|
try {
|
||||||
return Result.success(this.storage!.getItem(key) !== null);
|
return Result.ok(this.storage!.getItem(key) !== null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Result.failure(e);
|
return Result.err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* 通用 Result<T> 类型
|
|
||||||
*
|
|
||||||
* 对齐 Dart 项目中的 `sealed class Result<T>` / `Success<T>` / `Failure<T>` 模式。
|
|
||||||
* TypeScript 没有 sealed class,用判别联合(discriminated union)实现等价效果:
|
|
||||||
* 调用方在 `switch (result.kind)` 上做穷尽匹配,编译器在 strict 模式下能保证完整性。
|
|
||||||
*
|
|
||||||
* `error` 字段保持 `unknown`(不强制 `Error`)以匹配 Dart 端可抛任意对象的能力。
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type Result<T> =
|
|
||||||
| { readonly kind: "success"; readonly value: T }
|
|
||||||
| { readonly kind: "failure"; readonly error: unknown };
|
|
||||||
|
|
||||||
export const Result = {
|
|
||||||
success<T>(value: T): Result<T> {
|
|
||||||
return { kind: "success", value };
|
|
||||||
},
|
|
||||||
|
|
||||||
failure<T = never>(error: unknown): Result<T> {
|
|
||||||
return { kind: "failure", error };
|
|
||||||
},
|
|
||||||
|
|
||||||
isSuccess<T>(r: Result<T>): r is { kind: "success"; value: T } {
|
|
||||||
return r.kind === "success";
|
|
||||||
},
|
|
||||||
|
|
||||||
isFailure<T>(r: Result<T>): r is { kind: "failure"; error: unknown } {
|
|
||||||
return r.kind === "failure";
|
|
||||||
},
|
|
||||||
|
|
||||||
map<T, U>(r: Result<T>, fn: (v: T) => U): Result<U> {
|
|
||||||
return r.kind === "success" ? Result.success(fn(r.value)) : r;
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* 具体序列化由实现层通过 Zod schema 校验后转换为 `User` 类实例。
|
* 具体序列化由实现层通过 Zod schema 校验后转换为 `User` 类实例。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Result } from "../result";
|
import type { Result } from "@/utils/result";
|
||||||
import type { UserData } from "@/data/schemas/user/user";
|
import type { UserData } from "@/data/schemas/user/user";
|
||||||
|
|
||||||
export interface IUserStorage {
|
export interface IUserStorage {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { UserSchema, type UserData } from "@/data/schemas/user/user";
|
import { UserSchema, type UserData } from "@/data/schemas/user/user";
|
||||||
import { LocalStorage } from "../local_storage";
|
import { LocalStorage } from "../local_storage";
|
||||||
import { type Result as ResultT } from "../result";
|
import { type Result as ResultT } from "@/utils/result";
|
||||||
import { StorageKeys } from "../storage_keys";
|
import { StorageKeys } from "../storage_keys";
|
||||||
import type { IUserStorage } from "./iuser_storage";
|
import type { IUserStorage } from "./iuser_storage";
|
||||||
|
|
||||||
@@ -85,9 +85,9 @@ export class UserStorage implements IUserStorage {
|
|||||||
|
|
||||||
async clearUserData(): Promise<ResultT<void>> {
|
async clearUserData(): Promise<ResultT<void>> {
|
||||||
const r1 = await this.clearUser();
|
const r1 = await this.clearUser();
|
||||||
if (r1.kind === "failure") return r1;
|
if (!r1.success) return r1;
|
||||||
const r2 = await this.clearUserId();
|
const r2 = await this.clearUserId();
|
||||||
if (r2.kind === "failure") return r2;
|
if (!r2.success) return r2;
|
||||||
return this.clearAvatarUrl();
|
return this.clearAvatarUrl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user